Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b1d71f88 authored by Stanislav Zholnin's avatar Stanislav Zholnin
Browse files

Introduce caching to DiscreteRegistry.

Cached representation of state on disk is kept in the memory
after API query until next time data is dump to disk. Second and
following API queries will not hit the disk.
Other improvements and bug fixes.

Bug: 176965672
Test: in development
Change-Id: I583cba4c74487c79f023efd4b3ce2ee844351719
parent 884614ff
Loading
Loading
Loading
Loading
+242 −105
Original line number Diff line number Diff line
@@ -49,8 +49,6 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.XmlUtils;

import libcore.util.EmptyArray;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
@@ -85,6 +83,8 @@ final class DiscreteRegistry {
    private static final String TAG = DiscreteRegistry.class.getSimpleName();

    private static final long TIMELINE_HISTORY_CUTOFF = Duration.ofHours(24).toMillis();
    private static final long TIMELINE_QUANTIZATION = Duration.ofMinutes(1).toMillis();

    private static final String TAG_HISTORY = "h";
    private static final String ATTR_VERSION = "v";
    private static final int CURRENT_VERSION = 1;
@@ -107,6 +107,8 @@ final class DiscreteRegistry {
    private static final String ATTR_UID_STATE = "us";
    private static final String ATTR_FLAGS = "f";

    private static final int OP_FLAGS_DISCRETE = OP_FLAG_SELF | OP_FLAG_TRUSTED_PROXIED;

    // Lock for read/write access to on disk state
    private final Object mOnDiskLock = new Object();

@@ -119,6 +121,9 @@ final class DiscreteRegistry {
    @GuardedBy("mInMemoryLock")
    private DiscreteOps mDiscreteOps;

    @GuardedBy("mOnDiskLock")
    private DiscreteOps mCachedOps = null;

    DiscreteRegistry(Object inMemoryLock) {
        mInMemoryLock = inMemoryLock;
        mDiscreteAccessDir = new File(new File(Environment.getDataSystemDirectory(), "appops"),
@@ -173,18 +178,19 @@ final class DiscreteRegistry {
                    }
                }
            }
        }
            DiscreteOps discreteOps;
            synchronized (mInMemoryLock) {
                discreteOps = mDiscreteOps;
                mDiscreteOps = new DiscreteOps();
                mCachedOps = null;
            }
            if (discreteOps.isEmpty()) {
                return;
            }
            long currentTimeStamp = Instant.now().toEpochMilli();
            try {
            final File file = new File(mDiscreteAccessDir, currentTimeStamp + TIMELINE_FILE_SUFFIX);
                final File file = new File(mDiscreteAccessDir,
                        currentTimeStamp + TIMELINE_FILE_SUFFIX);
                discreteOps.writeToFile(file);
            } catch (Throwable t) {
                Slog.e(TAG,
@@ -192,30 +198,39 @@ final class DiscreteRegistry {
                                + Arrays.toString(t.getStackTrace()));
            }
        }
    }

    void getHistoricalDiscreteOps(AppOpsManager.HistoricalOps result, long beginTimeMillis,
            long endTimeMillis, @AppOpsManager.HistoricalOpsRequestFilter int filter, int uidFilter,
            @Nullable String packageNameFilter, @Nullable String[] opNamesFilter,
            @Nullable String attributionTagFilter, @AppOpsManager.OpFlags int flagsFilter) {
        writeAndClearAccessHistory();
        DiscreteOps discreteOps = new DiscreteOps();
        readDiscreteOpsFromDisk(discreteOps, beginTimeMillis, endTimeMillis, filter, uidFilter,
                packageNameFilter, opNamesFilter, attributionTagFilter, flagsFilter);
        DiscreteOps discreteOps = getAndCacheDiscreteOps();
        discreteOps.filter(beginTimeMillis, endTimeMillis, filter, uidFilter, packageNameFilter,
                opNamesFilter, attributionTagFilter, flagsFilter);
        discreteOps.applyToHistoricalOps(result);
        return;
    }

    private void readDiscreteOpsFromDisk(DiscreteOps discreteOps, long beginTimeMillis,
            long endTimeMillis, @AppOpsManager.HistoricalOpsRequestFilter int filter, int uidFilter,
            @Nullable String packageNameFilter, @Nullable String[] opNamesFilter,
            @Nullable String attributionTagFilter, @AppOpsManager.OpFlags int flagsFilter) {
    private DiscreteOps getAndCacheDiscreteOps() {
        DiscreteOps discreteOps = new DiscreteOps();

        synchronized (mOnDiskLock) {
            long historyBeginTimeMillis = Instant.now().minus(TIMELINE_HISTORY_CUTOFF,
                    ChronoUnit.MILLIS).toEpochMilli();
            if (historyBeginTimeMillis > endTimeMillis) {
                return;
            synchronized (mInMemoryLock) {
                discreteOps.merge(mDiscreteOps);
            }
            beginTimeMillis = max(beginTimeMillis, historyBeginTimeMillis);
            if (mCachedOps == null) {
                mCachedOps = new DiscreteOps();
                readDiscreteOpsFromDisk(mCachedOps);
            }
            discreteOps.merge(mCachedOps);
        }
        return discreteOps;
    }

    private void readDiscreteOpsFromDisk(DiscreteOps discreteOps) {
        synchronized (mOnDiskLock) {
            long beginTimeMillis = Instant.now().minus(TIMELINE_HISTORY_CUTOFF,
                    ChronoUnit.MILLIS).toEpochMilli();

            final File[] files = mDiscreteAccessDir.listFiles();
            if (files != null && files.length > 0) {
@@ -229,8 +244,7 @@ final class DiscreteRegistry {
                    if (timestamp < beginTimeMillis) {
                        continue;
                    }
                    discreteOps.readFromFile(f, beginTimeMillis, endTimeMillis, filter, uidFilter,
                            packageNameFilter, opNamesFilter, attributionTagFilter, flagsFilter);
                    discreteOps.readFromFile(f, beginTimeMillis);
                }
            }
        }
@@ -251,15 +265,11 @@ final class DiscreteRegistry {
            @AppOpsManager.HistoricalOpsRequestFilter int filter, int dumpOp,
            @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix,
            int nDiscreteOps) {
        DiscreteOps discreteOps = new DiscreteOps();
        synchronized (mOnDiskLock) {
            writeAndClearAccessHistory();
            String[] opNamesFilter = dumpOp == OP_NONE ? EmptyArray.STRING
        DiscreteOps discreteOps = getAndCacheDiscreteOps();
        String[] opNamesFilter = dumpOp == OP_NONE ? null
                : new String[]{AppOpsManager.opToPublicName(dumpOp)};
            readDiscreteOpsFromDisk(discreteOps, 0, Instant.now().toEpochMilli(), filter,
                    uidFilter, packageNameFilter, opNamesFilter, attributionTagFilter,
                    OP_FLAGS_ALL);
        }
        discreteOps.filter(0, Instant.now().toEpochMilli(), filter, uidFilter, packageNameFilter,
                opNamesFilter, attributionTagFilter, OP_FLAGS_ALL);
        discreteOps.dump(pw, sdf, date, prefix, nDiscreteOps);
    }

@@ -270,7 +280,7 @@ final class DiscreteRegistry {
        if (!isDiscreteUid(uid)) {
            return false;
        }
        if ((flags & (OP_FLAG_SELF | OP_FLAG_TRUSTED_PROXIED)) == 0) {
        if ((flags & (OP_FLAGS_DISCRETE)) == 0) {
            return false;
        }
        return true;
@@ -298,6 +308,19 @@ final class DiscreteRegistry {
            mUids = new ArrayMap<>();
        }

        boolean isEmpty() {
            return mUids.isEmpty();
        }

        void merge(DiscreteOps other) {
            int nUids = other.mUids.size();
            for (int i = 0; i < nUids; i++) {
                int uid = other.mUids.keyAt(i);
                DiscreteUidOps uidOps = other.mUids.valueAt(i);
                getOrCreateDiscreteUidOps(uid).merge(uidOps);
            }
        }

        void addDiscreteAccess(int op, int uid, @NonNull String packageName,
                @Nullable String attributionTag, @AppOpsManager.OpFlags int flags,
                @AppOpsManager.UidState int uidState, long accessTime, long accessDuration) {
@@ -305,6 +328,25 @@ final class DiscreteRegistry {
                    uidState, accessTime, accessDuration);
        }

        private void filter(long beginTimeMillis, long endTimeMillis,
                @AppOpsManager.HistoricalOpsRequestFilter int filter, int uidFilter,
                @Nullable String packageNameFilter, @Nullable String[] opNamesFilter,
                @Nullable String attributionTagFilter, @AppOpsManager.OpFlags int flagsFilter) {
            if ((filter & FILTER_BY_UID) != 0) {
                ArrayMap<Integer, DiscreteUidOps> uids = new ArrayMap<>();
                uids.put(uidFilter, getOrCreateDiscreteUidOps(uidFilter));
                mUids = uids;
            }
            int nUids = mUids.size();
            for (int i = nUids - 1; i >= 0; i--) {
                mUids.valueAt(i).filter(beginTimeMillis, endTimeMillis, filter, packageNameFilter,
                        opNamesFilter, attributionTagFilter, flagsFilter);
                if (mUids.valueAt(i).isEmpty()) {
                    mUids.removeAt(i);
                }
            }
        }

        private void applyToHistoricalOps(AppOpsManager.HistoricalOps result) {
            int nUids = mUids.size();
            for (int i = 0; i < nUids; i++) {
@@ -353,14 +395,7 @@ final class DiscreteRegistry {
            return result;
        }

        boolean isEmpty() {
            return mUids.isEmpty();
        }

        private void readFromFile(File f, long beginTimeMillis, long endTimeMillis,
                @AppOpsManager.HistoricalOpsRequestFilter int filter,
                int uidFilter, @Nullable String packageNameFilter, @Nullable String[] opNamesFilter,
                @Nullable String attributionTagFilter, @AppOpsManager.OpFlags int flagsFilter) {
        private void readFromFile(File f, long beginTimeMillis) {
            try {
                FileInputStream stream = new FileInputStream(f);
                TypedXmlPullParser parser = Xml.resolvePullParser(stream);
@@ -377,12 +412,7 @@ final class DiscreteRegistry {
                while (XmlUtils.nextElementWithin(parser, depth)) {
                    if (TAG_UID.equals(parser.getName())) {
                        int uid = parser.getAttributeInt(null, ATTR_UID, -1);
                        if ((filter & FILTER_BY_UID) != 0 && uid != uidFilter) {
                            continue;
                        }
                        getOrCreateDiscreteUidOps(uid).deserialize(parser, beginTimeMillis,
                                endTimeMillis, filter, packageNameFilter, opNamesFilter,
                                attributionTagFilter, flagsFilter);
                        getOrCreateDiscreteUidOps(uid).deserialize(parser, beginTimeMillis);
                    }
                }
            } catch (Throwable t) {
@@ -400,6 +430,38 @@ final class DiscreteRegistry {
            mPackages = new ArrayMap<>();
        }

        boolean isEmpty() {
            return mPackages.isEmpty();
        }

        void merge(DiscreteUidOps other) {
            int nPackages = other.mPackages.size();
            for (int i = 0; i < nPackages; i++) {
                String packageName = other.mPackages.keyAt(i);
                DiscretePackageOps p = other.mPackages.valueAt(i);
                getOrCreateDiscretePackageOps(packageName).merge(p);
            }
        }

        private void filter(long beginTimeMillis, long endTimeMillis,
                @AppOpsManager.HistoricalOpsRequestFilter int filter,
                @Nullable String packageNameFilter, @Nullable String[] opNamesFilter,
                @Nullable String attributionTagFilter, @AppOpsManager.OpFlags int flagsFilter) {
            if ((filter & FILTER_BY_PACKAGE_NAME) != 0) {
                ArrayMap<String, DiscretePackageOps> packages = new ArrayMap<>();
                packages.put(packageNameFilter, getOrCreateDiscretePackageOps(packageNameFilter));
                mPackages = packages;
            }
            int nPackages = mPackages.size();
            for (int i = nPackages - 1; i >= 0; i--) {
                mPackages.valueAt(i).filter(beginTimeMillis, endTimeMillis, filter, opNamesFilter,
                        attributionTagFilter, flagsFilter);
                if (mPackages.valueAt(i).isEmpty()) {
                    mPackages.removeAt(i);
                }
            }
        }

        void addDiscreteAccess(int op, @NonNull String packageName, @Nullable String attributionTag,
                @AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState,
                long accessTime, long accessDuration) {
@@ -445,22 +507,12 @@ final class DiscreteRegistry {
            }
        }

        void deserialize(TypedXmlPullParser parser, long beginTimeMillis,
                long endTimeMillis, @AppOpsManager.HistoricalOpsRequestFilter int filter,
                @Nullable String packageNameFilter,
                @Nullable String[] opNamesFilter, @Nullable String attributionTagFilter,
                @AppOpsManager.OpFlags int flagsFilter) throws Exception {
        void deserialize(TypedXmlPullParser parser, long beginTimeMillis) throws Exception {
            int depth = parser.getDepth();
            while (XmlUtils.nextElementWithin(parser, depth)) {
                if (TAG_PACKAGE.equals(parser.getName())) {
                    String packageName = parser.getAttributeValue(null, ATTR_PACKAGE_NAME);
                    if ((filter & FILTER_BY_PACKAGE_NAME) != 0
                            && !packageName.equals(packageNameFilter)) {
                        continue;
                    }
                    getOrCreateDiscretePackageOps(packageName).deserialize(parser, beginTimeMillis,
                            endTimeMillis, filter, opNamesFilter, attributionTagFilter,
                            flagsFilter);
                    getOrCreateDiscretePackageOps(packageName).deserialize(parser, beginTimeMillis);
                }
            }
        }
@@ -473,6 +525,10 @@ final class DiscreteRegistry {
            mPackageOps = new ArrayMap<>();
        }

        boolean isEmpty() {
            return mPackageOps.isEmpty();
        }

        void addDiscreteAccess(int op, @Nullable String attributionTag,
                @AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState,
                long accessTime, long accessDuration) {
@@ -480,6 +536,35 @@ final class DiscreteRegistry {
                    accessDuration);
        }

        void merge(DiscretePackageOps other) {
            int nOps = other.mPackageOps.size();
            for (int i = 0; i < nOps; i++) {
                int opId = other.mPackageOps.keyAt(i);
                DiscreteOp op = other.mPackageOps.valueAt(i);
                getOrCreateDiscreteOp(opId).merge(op);
            }
        }

        private void filter(long beginTimeMillis, long endTimeMillis,
                @AppOpsManager.HistoricalOpsRequestFilter int filter,
                @Nullable String[] opNamesFilter, @Nullable String attributionTagFilter,
                @AppOpsManager.OpFlags int flagsFilter) {
            int nOps = mPackageOps.size();
            for (int i = nOps - 1; i >= 0; i--) {
                int opId = mPackageOps.keyAt(i);
                if ((filter & FILTER_BY_OP_NAMES) != 0 && !ArrayUtils.contains(opNamesFilter,
                        AppOpsManager.opToPublicName(opId))) {
                    mPackageOps.removeAt(i);
                    continue;
                }
                mPackageOps.valueAt(i).filter(beginTimeMillis, endTimeMillis, filter,
                        attributionTagFilter, flagsFilter);
                if (mPackageOps.valueAt(i).isEmpty()) {
                    mPackageOps.removeAt(i);
                }
            }
        }

        private DiscreteOp getOrCreateDiscreteOp(int op) {
            DiscreteOp result = mPackageOps.get(op);
            if (result == null) {
@@ -519,20 +604,12 @@ final class DiscreteRegistry {
            }
        }

        void deserialize(TypedXmlPullParser parser, long beginTimeMillis, long endTimeMillis,
                @AppOpsManager.HistoricalOpsRequestFilter int filter,
                @Nullable String[] opNamesFilter, @Nullable String attributionTagFilter,
                @AppOpsManager.OpFlags int flagsFilter) throws Exception {
        void deserialize(TypedXmlPullParser parser, long beginTimeMillis) throws Exception {
            int depth = parser.getDepth();
            while (XmlUtils.nextElementWithin(parser, depth)) {
                if (TAG_OP.equals(parser.getName())) {
                    int op = parser.getAttributeInt(null, ATTR_OP_ID);
                    if ((filter & FILTER_BY_OP_NAMES) != 0 && !ArrayUtils.contains(opNamesFilter,
                            AppOpsManager.opToPublicName(op))) {
                        continue;
                    }
                    getOrCreateDiscreteOp(op).deserialize(parser, beginTimeMillis, endTimeMillis,
                            filter, attributionTagFilter, flagsFilter);
                    getOrCreateDiscreteOp(op).deserialize(parser, beginTimeMillis);
                }
            }
        }
@@ -545,31 +622,66 @@ final class DiscreteRegistry {
            mAttributedOps = new ArrayMap<>();
        }

        boolean isEmpty() {
            return mAttributedOps.isEmpty();
        }

        void merge(DiscreteOp other) {
            int nTags = other.mAttributedOps.size();
            for (int i = 0; i < nTags; i++) {
                String tag = other.mAttributedOps.keyAt(i);
                List<DiscreteOpEvent> otherEvents = other.mAttributedOps.valueAt(i);
                List<DiscreteOpEvent> events = getOrCreateDiscreteOpEventsList(tag);
                mAttributedOps.put(tag, stableListMerge(events, otherEvents));
            }
        }

        private void filter(long beginTimeMillis, long endTimeMillis,
                @AppOpsManager.HistoricalOpsRequestFilter int filter,
                @Nullable String attributionTagFilter, @AppOpsManager.OpFlags int flagsFilter) {
            if ((filter & FILTER_BY_ATTRIBUTION_TAG) != 0) {
                ArrayMap<String, List<DiscreteOpEvent>> attributedOps = new ArrayMap<>();
                attributedOps.put(attributionTagFilter,
                        getOrCreateDiscreteOpEventsList(attributionTagFilter));
                mAttributedOps = attributedOps;
            }

            int nTags = mAttributedOps.size();
            for (int i = nTags - 1; i >= 0; i--) {
                String tag = mAttributedOps.keyAt(i);
                List<DiscreteOpEvent> list = mAttributedOps.valueAt(i);
                list = filterEventsList(list, beginTimeMillis, endTimeMillis, flagsFilter);
                mAttributedOps.put(tag, list);
                if (list.size() == 0) {
                    mAttributedOps.removeAt(i);
                }
            }
        }

        void addDiscreteAccess(@Nullable String attributionTag,
                @AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState,
                long accessTime, long accessDuration) {
            List<DiscreteOpEvent> attributedOps = getOrCreateDiscreteOpEventsList(
                    attributionTag);
            accessTime = Instant.ofEpochMilli(accessTime).truncatedTo(
                    ChronoUnit.MINUTES).toEpochMilli();
            accessTime = accessTime / TIMELINE_QUANTIZATION * TIMELINE_QUANTIZATION;

            int nAttributedOps = attributedOps.size();
            for (int i = nAttributedOps - 1; i >= 0; i--) {
                DiscreteOpEvent previousOp = attributedOps.get(i);
                if (i == nAttributedOps - 1 && previousOp.mNoteTime == accessTime
                        && accessDuration > -1) {
                    // existing event with updated duration
                    attributedOps.remove(i);
                    break;
                }
            int i = nAttributedOps;
            for (; i > 0; i--) {
                DiscreteOpEvent previousOp = attributedOps.get(i - 1);
                if (previousOp.mNoteTime < accessTime) {
                    break;
                }
                if (previousOp.mOpFlag == flags && previousOp.mUidState == uidState) {
                    if (accessDuration != previousOp.mNoteDuration
                            && accessDuration > TIMELINE_QUANTIZATION) {
                        break;
                    } else {
                        return;
                    }
                }
            attributedOps.add(new DiscreteOpEvent(accessTime, accessDuration, uidState, flags));
            }
            attributedOps.add(i, new DiscreteOpEvent(accessTime, accessDuration, uidState, flags));
        }

        private List<DiscreteOpEvent> getOrCreateDiscreteOpEventsList(String attributionTag) {
@@ -633,18 +745,11 @@ final class DiscreteRegistry {
            }
        }

        void deserialize(TypedXmlPullParser parser, long beginTimeMillis, long endTimeMillis,
                @AppOpsManager.HistoricalOpsRequestFilter int filter,
                @Nullable String attributionTagFilter,
                @AppOpsManager.OpFlags int flagsFilter) throws Exception {
        void deserialize(TypedXmlPullParser parser, long beginTimeMillis) throws Exception {
            int outerDepth = parser.getDepth();
            while (XmlUtils.nextElementWithin(parser, outerDepth)) {
                if (TAG_TAG.equals(parser.getName())) {
                    String attributionTag = parser.getAttributeValue(null, ATTR_TAG);
                    if ((filter & FILTER_BY_ATTRIBUTION_TAG) != 0 && !attributionTag.equals(
                            attributionTagFilter)) {
                        continue;
                    }
                    List<DiscreteOpEvent> events = getOrCreateDiscreteOpEventsList(
                            attributionTag);
                    int innerDepth = parser.getDepth();
@@ -655,11 +760,7 @@ final class DiscreteRegistry {
                                    -1);
                            int uidState = parser.getAttributeInt(null, ATTR_UID_STATE);
                            int opFlags = parser.getAttributeInt(null, ATTR_FLAGS);
                            if ((flagsFilter & opFlags) == 0) {
                                continue;
                            }
                            if ((noteTime + noteDuration < beginTimeMillis
                                    && noteTime > endTimeMillis)) {
                            if (noteTime + noteDuration < beginTimeMillis) {
                                continue;
                            }
                            DiscreteOpEvent event = new DiscreteOpEvent(noteTime, noteDuration,
@@ -715,5 +816,41 @@ final class DiscreteRegistry {
            out.attributeInt(null, ATTR_FLAGS, mOpFlag);
        }
    }

    private static List<DiscreteOpEvent> stableListMerge(List<DiscreteOpEvent> a,
            List<DiscreteOpEvent> b) {
        int nA = a.size();
        int nB = b.size();
        int i = 0;
        int k = 0;
        List<DiscreteOpEvent> result = new ArrayList<>(nA + nB);
        while (i < nA || k < nB) {
            if (i == nA) {
                result.add(b.get(k++));
            } else if (k == nB) {
                result.add(a.get(i++));
            } else if (a.get(i).mNoteTime < b.get(k).mNoteTime) {
                result.add(a.get(i++));
            } else {
                result.add(b.get(k++));
            }
        }
        return result;
    }

    private static List<DiscreteOpEvent> filterEventsList(List<DiscreteOpEvent> list,
            long beginTimeMillis, long endTimeMillis, @AppOpsManager.OpFlags int flagsFilter) {
        int n = list.size();
        List<DiscreteOpEvent> result = new ArrayList<>(n);
        for (int i = 0; i < n; i++) {
            DiscreteOpEvent event = list.get(i);
            if ((event.mOpFlag & flagsFilter) != 0
                    && event.mNoteTime + event.mNoteDuration > beginTimeMillis
                    && event.mNoteTime < endTimeMillis) {
                result.add(event);
            }
        }
        return result;
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -532,7 +532,7 @@ final class HistoricalRegistry {
                        System.currentTimeMillis()).increaseAccessDuration(op, uid, packageName,
                        attributionTag, uidState, flags, increment);
                mDiscreteRegistry.recordDiscreteAccess(uid, packageName, op, attributionTag,
                        flags, uidState, increment, eventStartTime);
                        flags, uidState, eventStartTime, increment);
            }
        }
    }