diff --git a/services/core/java/com/android/server/appop/DiscreteRegistry.java b/services/core/java/com/android/server/appop/DiscreteRegistry.java index ed62abc7d7731..2b0157c88136d 100644 --- a/services/core/java/com/android/server/appop/DiscreteRegistry.java +++ b/services/core/java/com/android/server/appop/DiscreteRegistry.java @@ -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,23 +178,25 @@ final class DiscreteRegistry { } } } - } - DiscreteOps discreteOps; - synchronized (mInMemoryLock) { - discreteOps = mDiscreteOps; - mDiscreteOps = new DiscreteOps(); - } - if (discreteOps.isEmpty()) { - return; - } - long currentTimeStamp = Instant.now().toEpochMilli(); - try { - final File file = new File(mDiscreteAccessDir, currentTimeStamp + TIMELINE_FILE_SUFFIX); - discreteOps.writeToFile(file); - } catch (Throwable t) { - Slog.e(TAG, - "Error writing timeline state: " + t.getMessage() + " " - + Arrays.toString(t.getStackTrace())); + 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); + discreteOps.writeToFile(file); + } catch (Throwable t) { + Slog.e(TAG, + "Error writing timeline state: " + t.getMessage() + " " + + Arrays.toString(t.getStackTrace())); + } } } @@ -197,25 +204,33 @@ final class DiscreteRegistry { 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 - : new String[]{AppOpsManager.opToPublicName(dumpOp)}; - readDiscreteOpsFromDisk(discreteOps, 0, Instant.now().toEpochMilli(), filter, - uidFilter, packageNameFilter, opNamesFilter, attributionTagFilter, - OP_FLAGS_ALL); - } + DiscreteOps discreteOps = getAndCacheDiscreteOps(); + String[] opNamesFilter = dumpOp == OP_NONE ? null + : new String[]{AppOpsManager.opToPublicName(dumpOp)}; + 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 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 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 otherEvents = other.mAttributedOps.valueAt(i); + List 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> 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 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 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) { - return; + 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 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 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 stableListMerge(List a, + List b) { + int nA = a.size(); + int nB = b.size(); + int i = 0; + int k = 0; + List 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 filterEventsList(List list, + long beginTimeMillis, long endTimeMillis, @AppOpsManager.OpFlags int flagsFilter) { + int n = list.size(); + List 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; + } } diff --git a/services/core/java/com/android/server/appop/HistoricalRegistry.java b/services/core/java/com/android/server/appop/HistoricalRegistry.java index eeed4521f6c4b..4435c4795730c 100644 --- a/services/core/java/com/android/server/appop/HistoricalRegistry.java +++ b/services/core/java/com/android/server/appop/HistoricalRegistry.java @@ -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); } } }