From 24c5704a7dbf9f07eeb079f4b86634ce0c0c1d55 Mon Sep 17 00:00:00 2001 From: Jay Thomas Sullivan Date: Fri, 12 Aug 2022 18:16:12 -0700 Subject: [PATCH 1/3] Prepare to move AttributedOp out of AppOpsService These were preparation steps necessary before IntelliJ's refactoring tools would work to move the AttributedOp class out of AppOpsService. The steps were: - Injected AppOpsService into AttributedOp. - Any fields (or inner-classes) of AppOpsService that are directly referenced from AttributedOp were either changed from private to package-private, or changed to be accessed by getters/setters. Test: presubmit Change-Id: Ia725264c8dac07a4f0855f08af3cd2f3281a3b7e --- .../android/server/appop/AppOpsService.java | 107 ++++++++++-------- 1 file changed, 60 insertions(+), 47 deletions(-) diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index af73c2bac6f9b..9e90d19fd6a09 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -243,11 +243,11 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch /** Pool for {@link OpEventProxyInfoPool} to avoid to constantly reallocate new objects */ @GuardedBy("this") - private final OpEventProxyInfoPool mOpEventProxyInfoPool = new OpEventProxyInfoPool(); + final OpEventProxyInfoPool mOpEventProxyInfoPool = new OpEventProxyInfoPool(); /** Pool for {@link InProgressStartOpEventPool} to avoid to constantly reallocate new objects */ @GuardedBy("this") - private final InProgressStartOpEventPool mInProgressStartOpEventPool = + final InProgressStartOpEventPool mInProgressStartOpEventPool = new InProgressStartOpEventPool(); private final AppOpsManagerInternalImpl mAppOpsManagerInternal @@ -385,7 +385,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch /** * An unsynchronized pool of {@link OpEventProxyInfo} objects. */ - private class OpEventProxyInfoPool extends SimplePool { + class OpEventProxyInfoPool extends SimplePool { OpEventProxyInfoPool() { super(MAX_UNUSED_POOLED_OBJECTS); } @@ -405,7 +405,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch /** * An unsynchronized pool of {@link InProgressStartOpEvent} objects. */ - private class InProgressStartOpEventPool extends SimplePool { + class InProgressStartOpEventPool extends SimplePool { InProgressStartOpEventPool() { super(MAX_UNUSED_POOLED_OBJECTS); } @@ -653,7 +653,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } /** A in progress startOp->finishOp event */ - private static final class InProgressStartOpEvent implements IBinder.DeathRecipient { + static final class InProgressStartOpEvent implements IBinder.DeathRecipient { /** Wall clock time of startOp event (not monotonic) */ private long mStartTime; @@ -817,9 +817,18 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch public int getAttributionChainId() { return mAttributionChainId; } + + public void setStartTime(long startTime) { + mStartTime = startTime; + } + + public void setStartElapsedTime(long startElapsedTime) { + mStartElapsedTime = startElapsedTime; + } } - private final class AttributedOp { + final class AttributedOp { + private final @NonNull AppOpsService mAppOpsService; public final @Nullable String tag; public final @NonNull Op parent; @@ -845,7 +854,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch *

Key is clientId */ @GuardedBy("AppOpsService.this") - private @Nullable ArrayMap mInProgressEvents; + @Nullable ArrayMap mInProgressEvents; /** * Currently paused startOp events @@ -853,9 +862,11 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch *

Key is clientId */ @GuardedBy("AppOpsService.this") - private @Nullable ArrayMap mPausedInProgressEvents; + @Nullable ArrayMap mPausedInProgressEvents; - AttributedOp(@Nullable String tag, @NonNull Op parent) { + AttributedOp(@NonNull AppOpsService appOpsService, @Nullable String tag, + @NonNull Op parent) { + this.mAppOpsService = appOpsService; this.tag = tag; this.parent = parent; } @@ -876,9 +887,9 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch accessed(accessTime, -1, proxyUid, proxyPackageName, proxyAttributionTag, uidState, flags); - mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, parent.packageName, - tag, uidState, flags, accessTime, AppOpsManager.ATTRIBUTION_FLAGS_NONE, - AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE); + mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, + parent.packageName, tag, uidState, flags, accessTime, + AppOpsManager.ATTRIBUTION_FLAGS_NONE, AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE); } /** @@ -903,13 +914,14 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch OpEventProxyInfo proxyInfo = null; if (proxyUid != Process.INVALID_UID) { - proxyInfo = mOpEventProxyInfoPool.acquire(proxyUid, proxyPackageName, + proxyInfo = mAppOpsService.mOpEventProxyInfoPool.acquire(proxyUid, proxyPackageName, proxyAttributionTag); } NoteOpEvent existingEvent = mAccessEvents.get(key); if (existingEvent != null) { - existingEvent.reinit(noteTime, duration, proxyInfo, mOpEventProxyInfoPool); + existingEvent.reinit(noteTime, duration, proxyInfo, + mAppOpsService.mOpEventProxyInfoPool); } else { mAccessEvents.put(key, new NoteOpEvent(noteTime, duration, proxyInfo)); } @@ -924,8 +936,8 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch public void rejected(@AppOpsManager.UidState int uidState, @OpFlags int flags) { rejected(System.currentTimeMillis(), uidState, flags); - mHistoricalRegistry.incrementOpRejected(parent.op, parent.uid, parent.packageName, - tag, uidState, flags); + mAppOpsService.mHistoricalRegistry.incrementOpRejected(parent.op, parent.uid, + parent.packageName, tag, uidState, flags); } /** @@ -946,7 +958,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch // We do not collect proxy information for rejections yet NoteOpEvent existingEvent = mRejectEvents.get(key); if (existingEvent != null) { - existingEvent.reinit(noteTime, -1, null, mOpEventProxyInfoPool); + existingEvent.reinit(noteTime, -1, null, mAppOpsService.mOpEventProxyInfoPool); } else { mRejectEvents.put(key, new NoteOpEvent(noteTime, -1, null)); } @@ -989,8 +1001,8 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch boolean triggerCallbackIfNeeded, boolean isStarted, @AttributionFlags int attributionFlags, int attributionChainId) throws RemoteException { if (triggerCallbackIfNeeded && !parent.isRunning() && isStarted) { - scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid, parent.packageName, - tag, true, attributionFlags, attributionChainId); + mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid, + parent.packageName, tag, true, attributionFlags, attributionChainId); } if (isStarted && mInProgressEvents == null) { @@ -1004,14 +1016,14 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch long startTime = System.currentTimeMillis(); InProgressStartOpEvent event = events.get(clientId); if (event == null) { - event = mInProgressStartOpEventPool.acquire(startTime, + event = mAppOpsService.mInProgressStartOpEventPool.acquire(startTime, SystemClock.elapsedRealtime(), clientId, tag, PooledLambda.obtainRunnable(AppOpsService::onClientDeath, this, clientId), proxyUid, proxyPackageName, proxyAttributionTag, uidState, flags, attributionFlags, attributionChainId); events.put(clientId, event); } else { - if (uidState != event.mUidState) { + if (uidState != event.getUidState()) { onUidStateChanged(uidState); } } @@ -1019,7 +1031,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch event.numUnfinishedStarts++; if (isStarted) { - mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, + mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, parent.packageName, tag, uidState, flags, startTime, attributionFlags, attributionChainId); } @@ -1075,21 +1087,21 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch mAccessEvents.put(makeKey(event.getUidState(), event.getFlags()), finishedEvent); - mHistoricalRegistry.increaseOpAccessDuration(parent.op, parent.uid, + mAppOpsService.mHistoricalRegistry.increaseOpAccessDuration(parent.op, parent.uid, parent.packageName, tag, event.getUidState(), event.getFlags(), finishedEvent.getNoteTime(), finishedEvent.getDuration(), event.getAttributionFlags(), event.getAttributionChainId()); if (!isPausing) { - mInProgressStartOpEventPool.release(event); + mAppOpsService.mInProgressStartOpEventPool.release(event); if (mInProgressEvents.isEmpty()) { mInProgressEvents = null; // TODO ntmyren: Also callback for single attribution tag activity changes if (triggerCallbackIfNeeded && !parent.isRunning()) { - scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid, - parent.packageName, tag, false, event.getAttributionFlags(), - event.getAttributionChainId()); + mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, + parent.uid, parent.packageName, tag, false, + event.getAttributionFlags(), event.getAttributionChainId()); } } } @@ -1117,7 +1129,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch event.numUnfinishedStarts--; if (event.numUnfinishedStarts == 0) { mPausedInProgressEvents.removeAt(indexOfToken); - mInProgressStartOpEventPool.release(event); + mAppOpsService.mInProgressStartOpEventPool.release(event); if (mPausedInProgressEvents.isEmpty()) { mPausedInProgressEvents = null; } @@ -1149,10 +1161,10 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch for (int i = 0; i < mInProgressEvents.size(); i++) { InProgressStartOpEvent event = mInProgressEvents.valueAt(i); - mPausedInProgressEvents.put(event.mClientId, event); - finishOrPause(event.mClientId, true, true); + mPausedInProgressEvents.put(event.getClientId(), event); + finishOrPause(event.getClientId(), true, true); - scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid, + mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid, parent.packageName, tag, false, event.getAttributionFlags(), event.getAttributionChainId()); } @@ -1177,20 +1189,21 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch long startTime = System.currentTimeMillis(); for (int i = 0; i < mPausedInProgressEvents.size(); i++) { InProgressStartOpEvent event = mPausedInProgressEvents.valueAt(i); - mInProgressEvents.put(event.mClientId, event); - event.mStartElapsedTime = SystemClock.elapsedRealtime(); - event.mStartTime = startTime; - mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, - parent.packageName, tag, event.mUidState, event.mFlags, startTime, + mInProgressEvents.put(event.getClientId(), event); + event.setStartElapsedTime(SystemClock.elapsedRealtime()); + event.setStartTime(startTime); + mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, + parent.packageName, tag, event.getUidState(), event.getFlags(), startTime, event.getAttributionFlags(), event.getAttributionChainId()); if (shouldSendActive) { - scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid, parent.packageName, - tag, true, event.getAttributionFlags(), event.getAttributionChainId()); + mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid, + parent.packageName, tag, true, event.getAttributionFlags(), + event.getAttributionChainId()); } // Note: this always sends MODE_ALLOWED, even if the mode is FOREGROUND // TODO ntmyren: figure out how to get the real mode. - scheduleOpStartedIfNeededLocked(parent.op, parent.uid, parent.packageName, - tag, event.getFlags(), MODE_ALLOWED, START_TYPE_RESUMED, + mAppOpsService.scheduleOpStartedIfNeededLocked(parent.op, parent.uid, + parent.packageName, tag, event.getFlags(), MODE_ALLOWED, START_TYPE_RESUMED, event.getAttributionFlags(), event.getAttributionChainId()); } mPausedInProgressEvents = null; @@ -1202,7 +1215,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch * @param clientId The client that died */ void onClientDeath(@NonNull IBinder clientId) { - synchronized (AppOpsService.this) { + synchronized (mAppOpsService) { if (!isPaused() && !isRunning()) { return; } @@ -1320,7 +1333,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch InProgressStartOpEvent event = ignoredEvents.valueAt(i); event.finish(); - mInProgressStartOpEventPool.release(event); + mAppOpsService.mInProgressStartOpEventPool.release(event); } } @@ -1425,7 +1438,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch attributedOp = mAttributions.get(attributionTag); if (attributedOp == null) { - attributedOp = new AttributedOp(attributionTag, parent); + attributedOp = new AttributedOp(AppOpsService.this, attributionTag, parent); mAttributions.put(attributionTag, attributedOp); } @@ -1675,7 +1688,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch /** * Call {@link AttributedOp#onClientDeath attributedOp.onClientDeath(clientId)}. */ - private static void onClientDeath(@NonNull AttributedOp attributedOp, + static void onClientDeath(@NonNull AttributedOp attributedOp, @NonNull IBinder clientId) { attributedOp.onClientDeath(clientId); } @@ -1916,7 +1929,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch PackageInfo pi = getPackageManagerInternal().getPackageInfo(packageName, PackageManager.GET_PERMISSIONS, Process.myUid(), mContext.getUserId()); if (isSamplingTarget(pi)) { - synchronized (this) { + synchronized (AppOpsService.this) { mRarelyUsedPackages.add(packageName); } } @@ -4016,7 +4029,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } } - private void scheduleOpActiveChangedIfNeededLocked(int code, int uid, @NonNull + void scheduleOpActiveChangedIfNeededLocked(int code, int uid, @NonNull String packageName, @Nullable String attributionTag, boolean active, @AttributionFlags int attributionFlags, int attributionChainId) { ArraySet dispatchedCallbacks = null; @@ -4069,7 +4082,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } } - private void scheduleOpStartedIfNeededLocked(int code, int uid, String pkgName, + void scheduleOpStartedIfNeededLocked(int code, int uid, String pkgName, String attributionTag, @OpFlags int flags, @Mode int result, @AppOpsManager.OnOpStartedListener.StartedType int startedType, @AttributionFlags int attributionFlags, int attributionChainId) { From 4a56137f424965d1cab6f371883deb9b284fc507 Mon Sep 17 00:00:00 2001 From: Jay Thomas Sullivan Date: Fri, 12 Aug 2022 19:54:49 -0700 Subject: [PATCH 2/3] Refactor: move AttributedOp out of AppOpsService Currently, AttributedOp is an inner class of AppOpsService. Use the "Move Inner Class to Upper Level" refactor tool to to make it a top-level class. Test: presubmit Change-Id: I3cfe46ea205b944a500c1a877e71d4571a5a8ed9 --- .../android/server/appop/AppOpsService.java | 576 ---------------- .../android/server/appop/AttributedOp.java | 626 ++++++++++++++++++ 2 files changed, 626 insertions(+), 576 deletions(-) create mode 100644 services/core/java/com/android/server/appop/AttributedOp.java diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 9e90d19fd6a09..3d5711ff20f98 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -33,7 +33,6 @@ import static android.app.AppOpsManager.MODE_DEFAULT; import static android.app.AppOpsManager.MODE_ERRORED; import static android.app.AppOpsManager.MODE_FOREGROUND; import static android.app.AppOpsManager.MODE_IGNORED; -import static android.app.AppOpsManager.NoteOpEvent; import static android.app.AppOpsManager.OP_CAMERA; import static android.app.AppOpsManager.OP_FLAGS_ALL; import static android.app.AppOpsManager.OP_FLAG_SELF; @@ -45,7 +44,6 @@ import static android.app.AppOpsManager.OP_RECORD_AUDIO; import static android.app.AppOpsManager.OP_RECORD_AUDIO_HOTWORD; import static android.app.AppOpsManager.OP_VIBRATE; import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_FAILED; -import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_RESUMED; import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_STARTED; import static android.app.AppOpsManager.OpEventProxyInfo; import static android.app.AppOpsManager.RestrictionBypass; @@ -57,7 +55,6 @@ import static android.app.AppOpsManager.SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTIO import static android.app.AppOpsManager._NUM_OP; import static android.app.AppOpsManager.extractFlagsFromKey; import static android.app.AppOpsManager.extractUidStateFromKey; -import static android.app.AppOpsManager.makeKey; import static android.app.AppOpsManager.modeToName; import static android.app.AppOpsManager.opAllowSystemBypassRestriction; import static android.app.AppOpsManager.opRestrictsRead; @@ -131,7 +128,6 @@ import android.util.ArraySet; import android.util.AtomicFile; import android.util.IndentingPrintWriter; import android.util.KeyValueListParser; -import android.util.LongSparseArray; import android.util.Pair; import android.util.Pools; import android.util.Pools.SimplePool; @@ -827,578 +823,6 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } } - final class AttributedOp { - private final @NonNull AppOpsService mAppOpsService; - public final @Nullable String tag; - public final @NonNull Op parent; - - /** - * Last successful accesses (noteOp + finished startOp) for each uidState/opFlag combination - * - *

Key is {@link AppOpsManager#makeKey} - */ - @GuardedBy("AppOpsService.this") - private @Nullable LongSparseArray mAccessEvents; - - /** - * Last rejected accesses for each uidState/opFlag combination - * - *

Key is {@link AppOpsManager#makeKey} - */ - @GuardedBy("AppOpsService.this") - private @Nullable LongSparseArray mRejectEvents; - - /** - * Currently in progress startOp events - * - *

Key is clientId - */ - @GuardedBy("AppOpsService.this") - @Nullable ArrayMap mInProgressEvents; - - /** - * Currently paused startOp events - * - *

Key is clientId - */ - @GuardedBy("AppOpsService.this") - @Nullable ArrayMap mPausedInProgressEvents; - - AttributedOp(@NonNull AppOpsService appOpsService, @Nullable String tag, - @NonNull Op parent) { - this.mAppOpsService = appOpsService; - this.tag = tag; - this.parent = parent; - } - - /** - * Update state when noteOp was rejected or startOp->finishOp event finished - * - * @param proxyUid The uid of the proxy - * @param proxyPackageName The package name of the proxy - * @param proxyAttributionTag the attributionTag in the proxies package - * @param uidState UID state of the app noteOp/startOp was called for - * @param flags OpFlags of the call - */ - public void accessed(int proxyUid, @Nullable String proxyPackageName, - @Nullable String proxyAttributionTag, @AppOpsManager.UidState int uidState, - @OpFlags int flags) { - long accessTime = System.currentTimeMillis(); - accessed(accessTime, -1, proxyUid, proxyPackageName, - proxyAttributionTag, uidState, flags); - - mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, - parent.packageName, tag, uidState, flags, accessTime, - AppOpsManager.ATTRIBUTION_FLAGS_NONE, AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE); - } - - /** - * Add an access that was previously collected. - * - * @param noteTime The time of the event - * @param duration The duration of the event - * @param proxyUid The uid of the proxy - * @param proxyPackageName The package name of the proxy - * @param proxyAttributionTag the attributionTag in the proxies package - * @param uidState UID state of the app noteOp/startOp was called for - * @param flags OpFlags of the call - */ - public void accessed(long noteTime, long duration, int proxyUid, - @Nullable String proxyPackageName, @Nullable String proxyAttributionTag, - @AppOpsManager.UidState int uidState, @OpFlags int flags) { - long key = makeKey(uidState, flags); - - if (mAccessEvents == null) { - mAccessEvents = new LongSparseArray<>(1); - } - - OpEventProxyInfo proxyInfo = null; - if (proxyUid != Process.INVALID_UID) { - proxyInfo = mAppOpsService.mOpEventProxyInfoPool.acquire(proxyUid, proxyPackageName, - proxyAttributionTag); - } - - NoteOpEvent existingEvent = mAccessEvents.get(key); - if (existingEvent != null) { - existingEvent.reinit(noteTime, duration, proxyInfo, - mAppOpsService.mOpEventProxyInfoPool); - } else { - mAccessEvents.put(key, new NoteOpEvent(noteTime, duration, proxyInfo)); - } - } - - /** - * Update state when noteOp/startOp was rejected. - * - * @param uidState UID state of the app noteOp is called for - * @param flags OpFlags of the call - */ - public void rejected(@AppOpsManager.UidState int uidState, @OpFlags int flags) { - rejected(System.currentTimeMillis(), uidState, flags); - - mAppOpsService.mHistoricalRegistry.incrementOpRejected(parent.op, parent.uid, - parent.packageName, tag, uidState, flags); - } - - /** - * Add an rejection that was previously collected - * - * @param noteTime The time of the event - * @param uidState UID state of the app noteOp/startOp was called for - * @param flags OpFlags of the call - */ - public void rejected(long noteTime, @AppOpsManager.UidState int uidState, - @OpFlags int flags) { - long key = makeKey(uidState, flags); - - if (mRejectEvents == null) { - mRejectEvents = new LongSparseArray<>(1); - } - - // We do not collect proxy information for rejections yet - NoteOpEvent existingEvent = mRejectEvents.get(key); - if (existingEvent != null) { - existingEvent.reinit(noteTime, -1, null, mAppOpsService.mOpEventProxyInfoPool); - } else { - mRejectEvents.put(key, new NoteOpEvent(noteTime, -1, null)); - } - } - - /** - * Update state when start was called - * - * @param clientId Id of the startOp caller - * @param proxyUid The UID of the proxy app - * @param proxyPackageName The package name of the proxy app - * @param proxyAttributionTag The attribution tag of the proxy app - * @param uidState UID state of the app startOp is called for - * @param flags The proxy flags - * @param attributionFlags The attribution flags associated with this operation. - * @param attributionChainId The if of the attribution chain this operations is a part of. - */ - public void started(@NonNull IBinder clientId, int proxyUid, - @Nullable String proxyPackageName, @Nullable String proxyAttributionTag, - @AppOpsManager.UidState int uidState, @OpFlags int flags, @AttributionFlags - int attributionFlags, int attributionChainId) throws RemoteException { - started(clientId, proxyUid, proxyPackageName, proxyAttributionTag, - uidState, flags,/*triggerCallbackIfNeeded*/ true, attributionFlags, - attributionChainId); - } - - private void started(@NonNull IBinder clientId, int proxyUid, - @Nullable String proxyPackageName, @Nullable String proxyAttributionTag, - @AppOpsManager.UidState int uidState, @OpFlags int flags, - boolean triggerCallbackIfNeeded, @AttributionFlags int attributionFlags, - int attributionChainId) throws RemoteException { - startedOrPaused(clientId, proxyUid, proxyPackageName, - proxyAttributionTag, uidState, flags, triggerCallbackIfNeeded, - /*triggerCallbackIfNeeded*/ true, attributionFlags, attributionChainId); - } - - private void startedOrPaused(@NonNull IBinder clientId, int proxyUid, - @Nullable String proxyPackageName, @Nullable String proxyAttributionTag, - @AppOpsManager.UidState int uidState, @OpFlags int flags, - boolean triggerCallbackIfNeeded, boolean isStarted, @AttributionFlags - int attributionFlags, int attributionChainId) throws RemoteException { - if (triggerCallbackIfNeeded && !parent.isRunning() && isStarted) { - mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid, - parent.packageName, tag, true, attributionFlags, attributionChainId); - } - - if (isStarted && mInProgressEvents == null) { - mInProgressEvents = new ArrayMap<>(1); - } else if (!isStarted && mPausedInProgressEvents == null) { - mPausedInProgressEvents = new ArrayMap<>(1); - } - ArrayMap events = isStarted - ? mInProgressEvents : mPausedInProgressEvents; - - long startTime = System.currentTimeMillis(); - InProgressStartOpEvent event = events.get(clientId); - if (event == null) { - event = mAppOpsService.mInProgressStartOpEventPool.acquire(startTime, - SystemClock.elapsedRealtime(), clientId, tag, - PooledLambda.obtainRunnable(AppOpsService::onClientDeath, this, clientId), - proxyUid, proxyPackageName, proxyAttributionTag, uidState, flags, - attributionFlags, attributionChainId); - events.put(clientId, event); - } else { - if (uidState != event.getUidState()) { - onUidStateChanged(uidState); - } - } - - event.numUnfinishedStarts++; - - if (isStarted) { - mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, - parent.packageName, tag, uidState, flags, startTime, attributionFlags, - attributionChainId); - } - } - - /** - * Update state when finishOp was called. Will finish started ops, and delete paused ops. - * - * @param clientId Id of the finishOp caller - */ - public void finished(@NonNull IBinder clientId) { - finished(clientId, true); - } - - private void finished(@NonNull IBinder clientId, boolean triggerCallbackIfNeeded) { - finishOrPause(clientId, triggerCallbackIfNeeded, false); - } - - /** - * Update state when paused or finished is called. If pausing, it records the op as - * stopping in the HistoricalRegistry, but does not delete it. - */ - private void finishOrPause(@NonNull IBinder clientId, boolean triggerCallbackIfNeeded, - boolean isPausing) { - int indexOfToken = isRunning() ? mInProgressEvents.indexOfKey(clientId) : -1; - if (indexOfToken < 0) { - finishPossiblyPaused(clientId, isPausing); - return; - } - - InProgressStartOpEvent event = mInProgressEvents.valueAt(indexOfToken); - if (!isPausing) { - event.numUnfinishedStarts--; - } - // If we are pausing, create a NoteOpEvent, but don't change the InProgress event - if (event.numUnfinishedStarts == 0 || isPausing) { - if (!isPausing) { - event.finish(); - mInProgressEvents.removeAt(indexOfToken); - } - - if (mAccessEvents == null) { - mAccessEvents = new LongSparseArray<>(1); - } - - OpEventProxyInfo proxyCopy = event.getProxy() != null - ? new OpEventProxyInfo(event.getProxy()) : null; - - long accessDurationMillis = - SystemClock.elapsedRealtime() - event.getStartElapsedTime(); - NoteOpEvent finishedEvent = new NoteOpEvent(event.getStartTime(), - accessDurationMillis, proxyCopy); - mAccessEvents.put(makeKey(event.getUidState(), event.getFlags()), - finishedEvent); - - mAppOpsService.mHistoricalRegistry.increaseOpAccessDuration(parent.op, parent.uid, - parent.packageName, tag, event.getUidState(), - event.getFlags(), finishedEvent.getNoteTime(), finishedEvent.getDuration(), - event.getAttributionFlags(), event.getAttributionChainId()); - - if (!isPausing) { - mAppOpsService.mInProgressStartOpEventPool.release(event); - if (mInProgressEvents.isEmpty()) { - mInProgressEvents = null; - - // TODO ntmyren: Also callback for single attribution tag activity changes - if (triggerCallbackIfNeeded && !parent.isRunning()) { - mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, - parent.uid, parent.packageName, tag, false, - event.getAttributionFlags(), event.getAttributionChainId()); - } - } - } - } - } - - // Finish or pause (no-op) an already paused op - private void finishPossiblyPaused(@NonNull IBinder clientId, boolean isPausing) { - if (!isPaused()) { - Slog.wtf(TAG, "No ops running or paused"); - return; - } - - int indexOfToken = mPausedInProgressEvents.indexOfKey(clientId); - if (indexOfToken < 0) { - Slog.wtf(TAG, "No op running or paused for the client"); - return; - } else if (isPausing) { - // already paused - return; - } - - // no need to record a paused event finishing. - InProgressStartOpEvent event = mPausedInProgressEvents.valueAt(indexOfToken); - event.numUnfinishedStarts--; - if (event.numUnfinishedStarts == 0) { - mPausedInProgressEvents.removeAt(indexOfToken); - mAppOpsService.mInProgressStartOpEventPool.release(event); - if (mPausedInProgressEvents.isEmpty()) { - mPausedInProgressEvents = null; - } - } - } - - /** - * Create an event that will be started, if the op is unpaused. - */ - public void createPaused(@NonNull IBinder clientId, int proxyUid, - @Nullable String proxyPackageName, @Nullable String proxyAttributionTag, - @AppOpsManager.UidState int uidState, @OpFlags int flags, @AttributionFlags - int attributionFlags, int attributionChainId) throws RemoteException { - startedOrPaused(clientId, proxyUid, proxyPackageName, proxyAttributionTag, - uidState, flags, true, false, attributionFlags, attributionChainId); - } - - /** - * Pause all currently started ops. This will create a HistoricalRegistry - */ - public void pause() { - if (!isRunning()) { - return; - } - - if (mPausedInProgressEvents == null) { - mPausedInProgressEvents = new ArrayMap<>(1); - } - - for (int i = 0; i < mInProgressEvents.size(); i++) { - InProgressStartOpEvent event = mInProgressEvents.valueAt(i); - mPausedInProgressEvents.put(event.getClientId(), event); - finishOrPause(event.getClientId(), true, true); - - mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid, - parent.packageName, tag, false, - event.getAttributionFlags(), event.getAttributionChainId()); - } - mInProgressEvents = null; - } - - /** - * Unpause all currently paused ops. This will reinitialize their start and duration - * times, but keep all other values the same - */ - public void resume() { - if (!isPaused()) { - return; - } - - if (mInProgressEvents == null) { - mInProgressEvents = new ArrayMap<>(mPausedInProgressEvents.size()); - } - boolean shouldSendActive = !mPausedInProgressEvents.isEmpty() - && mInProgressEvents.isEmpty(); - - long startTime = System.currentTimeMillis(); - for (int i = 0; i < mPausedInProgressEvents.size(); i++) { - InProgressStartOpEvent event = mPausedInProgressEvents.valueAt(i); - mInProgressEvents.put(event.getClientId(), event); - event.setStartElapsedTime(SystemClock.elapsedRealtime()); - event.setStartTime(startTime); - mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, - parent.packageName, tag, event.getUidState(), event.getFlags(), startTime, - event.getAttributionFlags(), event.getAttributionChainId()); - if (shouldSendActive) { - mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid, - parent.packageName, tag, true, event.getAttributionFlags(), - event.getAttributionChainId()); - } - // Note: this always sends MODE_ALLOWED, even if the mode is FOREGROUND - // TODO ntmyren: figure out how to get the real mode. - mAppOpsService.scheduleOpStartedIfNeededLocked(parent.op, parent.uid, - parent.packageName, tag, event.getFlags(), MODE_ALLOWED, START_TYPE_RESUMED, - event.getAttributionFlags(), event.getAttributionChainId()); - } - mPausedInProgressEvents = null; - } - - /** - * Called in the case the client dies without calling finish first - * - * @param clientId The client that died - */ - void onClientDeath(@NonNull IBinder clientId) { - synchronized (mAppOpsService) { - if (!isPaused() && !isRunning()) { - return; - } - - ArrayMap events = isPaused() - ? mPausedInProgressEvents : mInProgressEvents; - InProgressStartOpEvent deadEvent = events.get(clientId); - if (deadEvent != null) { - deadEvent.numUnfinishedStarts = 1; - } - - finished(clientId); - } - } - - /** - * Notify that the state of the uid changed - * - * @param newState The new state - */ - public void onUidStateChanged(@AppOpsManager.UidState int newState) { - if (!isPaused() && !isRunning()) { - return; - } - - boolean isRunning = isRunning(); - ArrayMap events = - isRunning ? mInProgressEvents : mPausedInProgressEvents; - - int numInProgressEvents = events.size(); - List binders = new ArrayList<>(events.keySet()); - for (int i = 0; i < numInProgressEvents; i++) { - InProgressStartOpEvent event = events.get(binders.get(i)); - - if (event != null && event.getUidState() != newState) { - try { - // Remove all but one unfinished start count and then call finished() to - // remove start event object - int numPreviousUnfinishedStarts = event.numUnfinishedStarts; - event.numUnfinishedStarts = 1; - OpEventProxyInfo proxy = event.getProxy(); - - finished(event.getClientId(), false); - - // Call started() to add a new start event object and then add the - // previously removed unfinished start counts back - if (proxy != null) { - startedOrPaused(event.getClientId(), proxy.getUid(), - proxy.getPackageName(), proxy.getAttributionTag(), newState, - event.getFlags(), false, isRunning, - event.getAttributionFlags(), event.getAttributionChainId()); - } else { - startedOrPaused(event.getClientId(), Process.INVALID_UID, null, null, - newState, event.getFlags(), false, isRunning, - event.getAttributionFlags(), event.getAttributionChainId()); - } - - events = isRunning ? mInProgressEvents : mPausedInProgressEvents; - InProgressStartOpEvent newEvent = events.get(binders.get(i)); - if (newEvent != null) { - newEvent.numUnfinishedStarts += numPreviousUnfinishedStarts - 1; - } - } catch (RemoteException e) { - if (DEBUG) Slog.e(TAG, "Cannot switch to new uidState " + newState); - } - } - } - } - - /** - * Combine {@code a} and {@code b} and return the result. The result might be {@code a} - * or {@code b}. If there is an event for the same key in both the later event is retained. - */ - private @Nullable LongSparseArray add(@Nullable LongSparseArray a, - @Nullable LongSparseArray b) { - if (a == null) { - return b; - } - - if (b == null) { - return a; - } - - int numEventsToAdd = b.size(); - for (int i = 0; i < numEventsToAdd; i++) { - long keyOfEventToAdd = b.keyAt(i); - NoteOpEvent bEvent = b.valueAt(i); - NoteOpEvent aEvent = a.get(keyOfEventToAdd); - - if (aEvent == null || bEvent.getNoteTime() > aEvent.getNoteTime()) { - a.put(keyOfEventToAdd, bEvent); - } - } - - return a; - } - - /** - * Add all data from the {@code opToAdd} to this op. - * - *

If there is an event for the same key in both the later event is retained. - *

{@code opToAdd} should not be used after this method is called. - * - * @param opToAdd The op to add - */ - public void add(@NonNull AttributedOp opToAdd) { - if (opToAdd.isRunning() || opToAdd.isPaused()) { - ArrayMap ignoredEvents = opToAdd.isRunning() - ? opToAdd.mInProgressEvents : opToAdd.mPausedInProgressEvents; - Slog.w(TAG, "Ignoring " + ignoredEvents.size() + " app-ops, running: " - + opToAdd.isRunning()); - - int numInProgressEvents = ignoredEvents.size(); - for (int i = 0; i < numInProgressEvents; i++) { - InProgressStartOpEvent event = ignoredEvents.valueAt(i); - - event.finish(); - mAppOpsService.mInProgressStartOpEventPool.release(event); - } - } - - mAccessEvents = add(mAccessEvents, opToAdd.mAccessEvents); - mRejectEvents = add(mRejectEvents, opToAdd.mRejectEvents); - } - - public boolean isRunning() { - return mInProgressEvents != null && !mInProgressEvents.isEmpty(); - } - - public boolean isPaused() { - return mPausedInProgressEvents != null && !mPausedInProgressEvents.isEmpty(); - } - - boolean hasAnyTime() { - return (mAccessEvents != null && mAccessEvents.size() > 0) - || (mRejectEvents != null && mRejectEvents.size() > 0); - } - - /** - * Clone a {@link LongSparseArray} and clone all values. - */ - private @Nullable LongSparseArray deepClone( - @Nullable LongSparseArray original) { - if (original == null) { - return original; - } - - int size = original.size(); - LongSparseArray clone = new LongSparseArray<>(size); - for (int i = 0; i < size; i++) { - clone.put(original.keyAt(i), new NoteOpEvent(original.valueAt(i))); - } - - return clone; - } - - @NonNull AttributedOpEntry createAttributedOpEntryLocked() { - LongSparseArray accessEvents = deepClone(mAccessEvents); - - // Add in progress events as access events - if (isRunning()) { - long now = SystemClock.elapsedRealtime(); - int numInProgressEvents = mInProgressEvents.size(); - - if (accessEvents == null) { - accessEvents = new LongSparseArray<>(numInProgressEvents); - } - - for (int i = 0; i < numInProgressEvents; i++) { - InProgressStartOpEvent event = mInProgressEvents.valueAt(i); - - accessEvents.append(makeKey(event.getUidState(), event.getFlags()), - new NoteOpEvent(event.getStartTime(), now - event.getStartElapsedTime(), - event.getProxy())); - } - } - - LongSparseArray rejectEvents = deepClone(mRejectEvents); - - return new AttributedOpEntry(parent.op, isRunning(), accessEvents, rejectEvents); - } - } - final class Op { int op; int uid; diff --git a/services/core/java/com/android/server/appop/AttributedOp.java b/services/core/java/com/android/server/appop/AttributedOp.java new file mode 100644 index 0000000000000..c37876bc889f7 --- /dev/null +++ b/services/core/java/com/android/server/appop/AttributedOp.java @@ -0,0 +1,626 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.appop; + +import static android.app.AppOpsManager.MODE_ALLOWED; +import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_RESUMED; +import static android.app.AppOpsManager.makeKey; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.AppOpsManager; +import android.os.IBinder; +import android.os.Process; +import android.os.RemoteException; +import android.os.SystemClock; +import android.util.ArrayMap; +import android.util.LongSparseArray; +import android.util.Slog; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.function.pooled.PooledLambda; + +import java.util.ArrayList; +import java.util.List; + +final class AttributedOp { + private final @NonNull AppOpsService mAppOpsService; + public final @Nullable String tag; + public final @NonNull AppOpsService.Op parent; + + /** + * Last successful accesses (noteOp + finished startOp) for each uidState/opFlag combination + * + *

Key is {@link AppOpsManager#makeKey} + */ + @GuardedBy("mAppOpsService") + private @Nullable LongSparseArray mAccessEvents; + + /** + * Last rejected accesses for each uidState/opFlag combination + * + *

Key is {@link AppOpsManager#makeKey} + */ + @GuardedBy("mAppOpsService") + private @Nullable LongSparseArray mRejectEvents; + + /** + * Currently in progress startOp events + * + *

Key is clientId + */ + @GuardedBy("mAppOpsService") + @Nullable ArrayMap mInProgressEvents; + + /** + * Currently paused startOp events + * + *

Key is clientId + */ + @GuardedBy("mAppOpsService") + @Nullable ArrayMap mPausedInProgressEvents; + + AttributedOp(@NonNull AppOpsService appOpsService, @Nullable String tag, + @NonNull AppOpsService.Op parent) { + mAppOpsService = appOpsService; + this.tag = tag; + this.parent = parent; + } + + /** + * Update state when noteOp was rejected or startOp->finishOp event finished + * + * @param proxyUid The uid of the proxy + * @param proxyPackageName The package name of the proxy + * @param proxyAttributionTag the attributionTag in the proxies package + * @param uidState UID state of the app noteOp/startOp was called for + * @param flags OpFlags of the call + */ + public void accessed(int proxyUid, @Nullable String proxyPackageName, + @Nullable String proxyAttributionTag, @AppOpsManager.UidState int uidState, + @AppOpsManager.OpFlags int flags) { + long accessTime = System.currentTimeMillis(); + accessed(accessTime, -1, proxyUid, proxyPackageName, + proxyAttributionTag, uidState, flags); + + mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, + parent.packageName, tag, uidState, flags, accessTime, + AppOpsManager.ATTRIBUTION_FLAGS_NONE, AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE); + } + + /** + * Add an access that was previously collected. + * + * @param noteTime The time of the event + * @param duration The duration of the event + * @param proxyUid The uid of the proxy + * @param proxyPackageName The package name of the proxy + * @param proxyAttributionTag the attributionTag in the proxies package + * @param uidState UID state of the app noteOp/startOp was called for + * @param flags OpFlags of the call + */ + @SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService + public void accessed(long noteTime, long duration, int proxyUid, + @Nullable String proxyPackageName, @Nullable String proxyAttributionTag, + @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags) { + long key = makeKey(uidState, flags); + + if (mAccessEvents == null) { + mAccessEvents = new LongSparseArray<>(1); + } + + AppOpsManager.OpEventProxyInfo proxyInfo = null; + if (proxyUid != Process.INVALID_UID) { + proxyInfo = mAppOpsService.mOpEventProxyInfoPool.acquire(proxyUid, proxyPackageName, + proxyAttributionTag); + } + + AppOpsManager.NoteOpEvent existingEvent = mAccessEvents.get(key); + if (existingEvent != null) { + existingEvent.reinit(noteTime, duration, proxyInfo, + mAppOpsService.mOpEventProxyInfoPool); + } else { + mAccessEvents.put(key, new AppOpsManager.NoteOpEvent(noteTime, duration, proxyInfo)); + } + } + + /** + * Update state when noteOp/startOp was rejected. + * + * @param uidState UID state of the app noteOp is called for + * @param flags OpFlags of the call + */ + public void rejected(@AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags) { + rejected(System.currentTimeMillis(), uidState, flags); + + mAppOpsService.mHistoricalRegistry.incrementOpRejected(parent.op, parent.uid, + parent.packageName, tag, uidState, flags); + } + + /** + * Add an rejection that was previously collected + * + * @param noteTime The time of the event + * @param uidState UID state of the app noteOp/startOp was called for + * @param flags OpFlags of the call + */ + @SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService + public void rejected(long noteTime, @AppOpsManager.UidState int uidState, + @AppOpsManager.OpFlags int flags) { + long key = makeKey(uidState, flags); + + if (mRejectEvents == null) { + mRejectEvents = new LongSparseArray<>(1); + } + + // We do not collect proxy information for rejections yet + AppOpsManager.NoteOpEvent existingEvent = mRejectEvents.get(key); + if (existingEvent != null) { + existingEvent.reinit(noteTime, -1, null, mAppOpsService.mOpEventProxyInfoPool); + } else { + mRejectEvents.put(key, new AppOpsManager.NoteOpEvent(noteTime, -1, null)); + } + } + + /** + * Update state when start was called + * + * @param clientId Id of the startOp caller + * @param proxyUid The UID of the proxy app + * @param proxyPackageName The package name of the proxy app + * @param proxyAttributionTag The attribution tag of the proxy app + * @param uidState UID state of the app startOp is called for + * @param flags The proxy flags + * @param attributionFlags The attribution flags associated with this operation. + * @param attributionChainId The if of the attribution chain this operations is a part of. + */ + public void started(@NonNull IBinder clientId, int proxyUid, + @Nullable String proxyPackageName, @Nullable String proxyAttributionTag, + @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags, + @AppOpsManager.AttributionFlags + int attributionFlags, int attributionChainId) throws RemoteException { + started(clientId, proxyUid, proxyPackageName, proxyAttributionTag, + uidState, flags, /*triggerCallbackIfNeeded*/ true, attributionFlags, + attributionChainId); + } + + private void started(@NonNull IBinder clientId, int proxyUid, + @Nullable String proxyPackageName, @Nullable String proxyAttributionTag, + @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags, + boolean triggerCallbackIfNeeded, @AppOpsManager.AttributionFlags int attributionFlags, + int attributionChainId) throws RemoteException { + startedOrPaused(clientId, proxyUid, proxyPackageName, + proxyAttributionTag, uidState, flags, triggerCallbackIfNeeded, + /*triggerCallbackIfNeeded*/ true, attributionFlags, attributionChainId); + } + + @SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService + private void startedOrPaused(@NonNull IBinder clientId, int proxyUid, + @Nullable String proxyPackageName, @Nullable String proxyAttributionTag, + @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags, + boolean triggerCallbackIfNeeded, boolean isStarted, @AppOpsManager.AttributionFlags + int attributionFlags, int attributionChainId) throws RemoteException { + if (triggerCallbackIfNeeded && !parent.isRunning() && isStarted) { + mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid, + parent.packageName, tag, true, attributionFlags, attributionChainId); + } + + if (isStarted && mInProgressEvents == null) { + mInProgressEvents = new ArrayMap<>(1); + } else if (!isStarted && mPausedInProgressEvents == null) { + mPausedInProgressEvents = new ArrayMap<>(1); + } + ArrayMap events = isStarted + ? mInProgressEvents : mPausedInProgressEvents; + + long startTime = System.currentTimeMillis(); + AppOpsService.InProgressStartOpEvent event = events.get(clientId); + if (event == null) { + event = mAppOpsService.mInProgressStartOpEventPool.acquire(startTime, + SystemClock.elapsedRealtime(), clientId, tag, + PooledLambda.obtainRunnable(AppOpsService::onClientDeath, this, clientId), + proxyUid, proxyPackageName, proxyAttributionTag, uidState, flags, + attributionFlags, attributionChainId); + events.put(clientId, event); + } else { + if (uidState != event.getUidState()) { + onUidStateChanged(uidState); + } + } + + event.numUnfinishedStarts++; + + if (isStarted) { + mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, + parent.packageName, tag, uidState, flags, startTime, attributionFlags, + attributionChainId); + } + } + + /** + * Update state when finishOp was called. Will finish started ops, and delete paused ops. + * + * @param clientId Id of the finishOp caller + */ + public void finished(@NonNull IBinder clientId) { + finished(clientId, true); + } + + private void finished(@NonNull IBinder clientId, boolean triggerCallbackIfNeeded) { + finishOrPause(clientId, triggerCallbackIfNeeded, false); + } + + /** + * Update state when paused or finished is called. If pausing, it records the op as + * stopping in the HistoricalRegistry, but does not delete it. + */ + @SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService + private void finishOrPause(@NonNull IBinder clientId, boolean triggerCallbackIfNeeded, + boolean isPausing) { + int indexOfToken = isRunning() ? mInProgressEvents.indexOfKey(clientId) : -1; + if (indexOfToken < 0) { + finishPossiblyPaused(clientId, isPausing); + return; + } + + AppOpsService.InProgressStartOpEvent event = mInProgressEvents.valueAt(indexOfToken); + if (!isPausing) { + event.numUnfinishedStarts--; + } + // If we are pausing, create a NoteOpEvent, but don't change the InProgress event + if (event.numUnfinishedStarts == 0 || isPausing) { + if (!isPausing) { + event.finish(); + mInProgressEvents.removeAt(indexOfToken); + } + + if (mAccessEvents == null) { + mAccessEvents = new LongSparseArray<>(1); + } + + AppOpsManager.OpEventProxyInfo proxyCopy = event.getProxy() != null + ? new AppOpsManager.OpEventProxyInfo(event.getProxy()) : null; + + long accessDurationMillis = + SystemClock.elapsedRealtime() - event.getStartElapsedTime(); + AppOpsManager.NoteOpEvent finishedEvent = new AppOpsManager.NoteOpEvent( + event.getStartTime(), + accessDurationMillis, proxyCopy); + mAccessEvents.put(makeKey(event.getUidState(), event.getFlags()), + finishedEvent); + + mAppOpsService.mHistoricalRegistry.increaseOpAccessDuration(parent.op, parent.uid, + parent.packageName, tag, event.getUidState(), + event.getFlags(), finishedEvent.getNoteTime(), finishedEvent.getDuration(), + event.getAttributionFlags(), event.getAttributionChainId()); + + if (!isPausing) { + mAppOpsService.mInProgressStartOpEventPool.release(event); + if (mInProgressEvents.isEmpty()) { + mInProgressEvents = null; + + // TODO ntmyren: Also callback for single attribution tag activity changes + if (triggerCallbackIfNeeded && !parent.isRunning()) { + mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, + parent.uid, parent.packageName, tag, false, + event.getAttributionFlags(), event.getAttributionChainId()); + } + } + } + } + } + + // Finish or pause (no-op) an already paused op + @SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService + private void finishPossiblyPaused(@NonNull IBinder clientId, boolean isPausing) { + if (!isPaused()) { + Slog.wtf(AppOpsService.TAG, "No ops running or paused"); + return; + } + + int indexOfToken = mPausedInProgressEvents.indexOfKey(clientId); + if (indexOfToken < 0) { + Slog.wtf(AppOpsService.TAG, "No op running or paused for the client"); + return; + } else if (isPausing) { + // already paused + return; + } + + // no need to record a paused event finishing. + AppOpsService.InProgressStartOpEvent event = mPausedInProgressEvents.valueAt(indexOfToken); + event.numUnfinishedStarts--; + if (event.numUnfinishedStarts == 0) { + mPausedInProgressEvents.removeAt(indexOfToken); + mAppOpsService.mInProgressStartOpEventPool.release(event); + if (mPausedInProgressEvents.isEmpty()) { + mPausedInProgressEvents = null; + } + } + } + + /** + * Create an event that will be started, if the op is unpaused. + */ + public void createPaused(@NonNull IBinder clientId, int proxyUid, + @Nullable String proxyPackageName, @Nullable String proxyAttributionTag, + @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags, + @AppOpsManager.AttributionFlags + int attributionFlags, int attributionChainId) throws RemoteException { + startedOrPaused(clientId, proxyUid, proxyPackageName, proxyAttributionTag, + uidState, flags, true, false, attributionFlags, attributionChainId); + } + + /** + * Pause all currently started ops. This will create a HistoricalRegistry + */ + public void pause() { + if (!isRunning()) { + return; + } + + if (mPausedInProgressEvents == null) { + mPausedInProgressEvents = new ArrayMap<>(1); + } + + for (int i = 0; i < mInProgressEvents.size(); i++) { + AppOpsService.InProgressStartOpEvent event = mInProgressEvents.valueAt(i); + mPausedInProgressEvents.put(event.getClientId(), event); + finishOrPause(event.getClientId(), true, true); + + mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid, + parent.packageName, tag, false, + event.getAttributionFlags(), event.getAttributionChainId()); + } + mInProgressEvents = null; + } + + /** + * Unpause all currently paused ops. This will reinitialize their start and duration + * times, but keep all other values the same + */ + public void resume() { + if (!isPaused()) { + return; + } + + if (mInProgressEvents == null) { + mInProgressEvents = new ArrayMap<>(mPausedInProgressEvents.size()); + } + boolean shouldSendActive = !mPausedInProgressEvents.isEmpty() + && mInProgressEvents.isEmpty(); + + long startTime = System.currentTimeMillis(); + for (int i = 0; i < mPausedInProgressEvents.size(); i++) { + AppOpsService.InProgressStartOpEvent event = mPausedInProgressEvents.valueAt(i); + mInProgressEvents.put(event.getClientId(), event); + event.setStartElapsedTime(SystemClock.elapsedRealtime()); + event.setStartTime(startTime); + mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, + parent.packageName, tag, event.getUidState(), event.getFlags(), startTime, + event.getAttributionFlags(), event.getAttributionChainId()); + if (shouldSendActive) { + mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid, + parent.packageName, tag, true, event.getAttributionFlags(), + event.getAttributionChainId()); + } + // Note: this always sends MODE_ALLOWED, even if the mode is FOREGROUND + // TODO ntmyren: figure out how to get the real mode. + mAppOpsService.scheduleOpStartedIfNeededLocked(parent.op, parent.uid, + parent.packageName, tag, event.getFlags(), MODE_ALLOWED, START_TYPE_RESUMED, + event.getAttributionFlags(), event.getAttributionChainId()); + } + mPausedInProgressEvents = null; + } + + /** + * Called in the case the client dies without calling finish first + * + * @param clientId The client that died + */ + void onClientDeath(@NonNull IBinder clientId) { + synchronized (mAppOpsService) { + if (!isPaused() && !isRunning()) { + return; + } + + ArrayMap events = isPaused() + ? mPausedInProgressEvents : mInProgressEvents; + AppOpsService.InProgressStartOpEvent deadEvent = events.get(clientId); + if (deadEvent != null) { + deadEvent.numUnfinishedStarts = 1; + } + + finished(clientId); + } + } + + /** + * Notify that the state of the uid changed + * + * @param newState The new state + */ + public void onUidStateChanged(@AppOpsManager.UidState int newState) { + if (!isPaused() && !isRunning()) { + return; + } + + boolean isRunning = isRunning(); + ArrayMap events = + isRunning ? mInProgressEvents : mPausedInProgressEvents; + + int numInProgressEvents = events.size(); + List binders = new ArrayList<>(events.keySet()); + for (int i = 0; i < numInProgressEvents; i++) { + AppOpsService.InProgressStartOpEvent event = events.get(binders.get(i)); + + if (event != null && event.getUidState() != newState) { + try { + // Remove all but one unfinished start count and then call finished() to + // remove start event object + int numPreviousUnfinishedStarts = event.numUnfinishedStarts; + event.numUnfinishedStarts = 1; + AppOpsManager.OpEventProxyInfo proxy = event.getProxy(); + + finished(event.getClientId(), false); + + // Call started() to add a new start event object and then add the + // previously removed unfinished start counts back + if (proxy != null) { + startedOrPaused(event.getClientId(), proxy.getUid(), + proxy.getPackageName(), proxy.getAttributionTag(), newState, + event.getFlags(), false, isRunning, + event.getAttributionFlags(), event.getAttributionChainId()); + } else { + startedOrPaused(event.getClientId(), Process.INVALID_UID, null, null, + newState, event.getFlags(), false, isRunning, + event.getAttributionFlags(), event.getAttributionChainId()); + } + + events = isRunning ? mInProgressEvents : mPausedInProgressEvents; + AppOpsService.InProgressStartOpEvent newEvent = events.get(binders.get(i)); + if (newEvent != null) { + newEvent.numUnfinishedStarts += numPreviousUnfinishedStarts - 1; + } + } catch (RemoteException e) { + if (AppOpsService.DEBUG) { + Slog.e(AppOpsService.TAG, + "Cannot switch to new uidState " + newState); + } + } + } + } + } + + /** + * Combine {@code a} and {@code b} and return the result. The result might be {@code a} + * or {@code b}. If there is an event for the same key in both the later event is retained. + */ + private @Nullable LongSparseArray add( + @Nullable LongSparseArray a, + @Nullable LongSparseArray b) { + if (a == null) { + return b; + } + + if (b == null) { + return a; + } + + int numEventsToAdd = b.size(); + for (int i = 0; i < numEventsToAdd; i++) { + long keyOfEventToAdd = b.keyAt(i); + AppOpsManager.NoteOpEvent bEvent = b.valueAt(i); + AppOpsManager.NoteOpEvent aEvent = a.get(keyOfEventToAdd); + + if (aEvent == null || bEvent.getNoteTime() > aEvent.getNoteTime()) { + a.put(keyOfEventToAdd, bEvent); + } + } + + return a; + } + + /** + * Add all data from the {@code opToAdd} to this op. + * + *

If there is an event for the same key in both the later event is retained. + *

{@code opToAdd} should not be used after this method is called. + * + * @param opToAdd The op to add + */ + @SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService + public void add(@NonNull AttributedOp opToAdd) { + if (opToAdd.isRunning() || opToAdd.isPaused()) { + ArrayMap ignoredEvents = + opToAdd.isRunning() + ? opToAdd.mInProgressEvents : opToAdd.mPausedInProgressEvents; + Slog.w(AppOpsService.TAG, "Ignoring " + ignoredEvents.size() + " app-ops, running: " + + opToAdd.isRunning()); + + int numInProgressEvents = ignoredEvents.size(); + for (int i = 0; i < numInProgressEvents; i++) { + AppOpsService.InProgressStartOpEvent event = ignoredEvents.valueAt(i); + + event.finish(); + mAppOpsService.mInProgressStartOpEventPool.release(event); + } + } + + mAccessEvents = add(mAccessEvents, opToAdd.mAccessEvents); + mRejectEvents = add(mRejectEvents, opToAdd.mRejectEvents); + } + + public boolean isRunning() { + return mInProgressEvents != null && !mInProgressEvents.isEmpty(); + } + + public boolean isPaused() { + return mPausedInProgressEvents != null && !mPausedInProgressEvents.isEmpty(); + } + + boolean hasAnyTime() { + return (mAccessEvents != null && mAccessEvents.size() > 0) + || (mRejectEvents != null && mRejectEvents.size() > 0); + } + + /** + * Clone a {@link LongSparseArray} and clone all values. + */ + private @Nullable LongSparseArray deepClone( + @Nullable LongSparseArray original) { + if (original == null) { + return original; + } + + int size = original.size(); + LongSparseArray clone = new LongSparseArray<>(size); + for (int i = 0; i < size; i++) { + clone.put(original.keyAt(i), new AppOpsManager.NoteOpEvent(original.valueAt(i))); + } + + return clone; + } + + @NonNull AppOpsManager.AttributedOpEntry createAttributedOpEntryLocked() { + LongSparseArray accessEvents = deepClone(mAccessEvents); + + // Add in progress events as access events + if (isRunning()) { + long now = SystemClock.elapsedRealtime(); + int numInProgressEvents = mInProgressEvents.size(); + + if (accessEvents == null) { + accessEvents = new LongSparseArray<>(numInProgressEvents); + } + + for (int i = 0; i < numInProgressEvents; i++) { + AppOpsService.InProgressStartOpEvent event = mInProgressEvents.valueAt(i); + + accessEvents.append(makeKey(event.getUidState(), event.getFlags()), + new AppOpsManager.NoteOpEvent(event.getStartTime(), + now - event.getStartElapsedTime(), + event.getProxy())); + } + } + + LongSparseArray rejectEvents = deepClone(mRejectEvents); + + return new AppOpsManager.AttributedOpEntry(parent.op, isRunning(), accessEvents, + rejectEvents); + } +} From 8e26e9a88f058eb0f5fc88a75234f63dd69d98af Mon Sep 17 00:00:00 2001 From: Jay Thomas Sullivan Date: Fri, 9 Sep 2022 16:22:10 -0700 Subject: [PATCH 3/3] Move related classes into AttributedOp Move InProgressStartOpEvent, InProgressStartOpEventPool, and OpEventProxyInfoPool into the AttributedOp class. These are currently in AppOpsService, but are only ever used by AttributedOp. Moving them reduces the amount of noise in AppOpsService. Test: presubmit Change-Id: Ic4e03b82c481c73914470d143c733a0ea243a72d --- .../android/server/appop/AppOpsService.java | 256 +-------------- .../android/server/appop/AttributedOp.java | 304 ++++++++++++++++-- 2 files changed, 290 insertions(+), 270 deletions(-) diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 3d5711ff20f98..b844fc6b43ab4 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -68,7 +68,6 @@ import static android.content.pm.PermissionInfo.PROTECTION_FLAG_APPOP; import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS; import android.Manifest; -import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; @@ -129,8 +128,6 @@ import android.util.AtomicFile; import android.util.IndentingPrintWriter; import android.util.KeyValueListParser; import android.util.Pair; -import android.util.Pools; -import android.util.Pools.SimplePool; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; @@ -194,7 +191,6 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.NoSuchElementException; import java.util.Objects; import java.util.Scanner; import java.util.Set; @@ -237,14 +233,22 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch private final @Nullable File mNoteOpCallerStacktracesFile; final Handler mHandler; - /** Pool for {@link OpEventProxyInfoPool} to avoid to constantly reallocate new objects */ + /** + * Pool for {@link AttributedOp.OpEventProxyInfoPool} to avoid to constantly reallocate new + * objects + */ @GuardedBy("this") - final OpEventProxyInfoPool mOpEventProxyInfoPool = new OpEventProxyInfoPool(); + final AttributedOp.OpEventProxyInfoPool mOpEventProxyInfoPool = + new AttributedOp.OpEventProxyInfoPool(MAX_UNUSED_POOLED_OBJECTS); - /** Pool for {@link InProgressStartOpEventPool} to avoid to constantly reallocate new objects */ + /** + * Pool for {@link AttributedOp.InProgressStartOpEventPool} to avoid to constantly reallocate + * new objects + */ @GuardedBy("this") - final InProgressStartOpEventPool mInProgressStartOpEventPool = - new InProgressStartOpEventPool(); + final AttributedOp.InProgressStartOpEventPool mInProgressStartOpEventPool = + new AttributedOp.InProgressStartOpEventPool(mOpEventProxyInfoPool, + MAX_UNUSED_POOLED_OBJECTS); private final AppOpsManagerInternalImpl mAppOpsManagerInternal = new AppOpsManagerInternalImpl(); @@ -378,60 +382,6 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch return mUidStateTracker; } - /** - * An unsynchronized pool of {@link OpEventProxyInfo} objects. - */ - class OpEventProxyInfoPool extends SimplePool { - OpEventProxyInfoPool() { - super(MAX_UNUSED_POOLED_OBJECTS); - } - - OpEventProxyInfo acquire(@IntRange(from = 0) int uid, @Nullable String packageName, - @Nullable String attributionTag) { - OpEventProxyInfo recycled = acquire(); - if (recycled != null) { - recycled.reinit(uid, packageName, attributionTag); - return recycled; - } - - return new OpEventProxyInfo(uid, packageName, attributionTag); - } - } - - /** - * An unsynchronized pool of {@link InProgressStartOpEvent} objects. - */ - class InProgressStartOpEventPool extends SimplePool { - InProgressStartOpEventPool() { - super(MAX_UNUSED_POOLED_OBJECTS); - } - - InProgressStartOpEvent acquire(long startTime, long elapsedTime, @NonNull IBinder clientId, - @Nullable String attributionTag, @NonNull Runnable onDeath, int proxyUid, - @Nullable String proxyPackageName, @Nullable String proxyAttributionTag, - @AppOpsManager.UidState int uidState, @OpFlags int flags, @AttributionFlags - int attributionFlags, int attributionChainId) throws RemoteException { - - InProgressStartOpEvent recycled = acquire(); - - OpEventProxyInfo proxyInfo = null; - if (proxyUid != Process.INVALID_UID) { - proxyInfo = mOpEventProxyInfoPool.acquire(proxyUid, proxyPackageName, - proxyAttributionTag); - } - - if (recycled != null) { - recycled.reinit(startTime, elapsedTime, clientId, attributionTag, onDeath, - uidState, flags, proxyInfo, attributionFlags, attributionChainId, - mOpEventProxyInfoPool); - return recycled; - } - - return new InProgressStartOpEvent(startTime, elapsedTime, clientId, attributionTag, - onDeath, uidState, proxyInfo, flags, attributionFlags, attributionChainId); - } - } - /** * All times are in milliseconds. These constants are kept synchronized with the system * global Settings. Any access to this class or its fields should be done while @@ -648,181 +598,6 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } } - /** A in progress startOp->finishOp event */ - static final class InProgressStartOpEvent implements IBinder.DeathRecipient { - /** Wall clock time of startOp event (not monotonic) */ - private long mStartTime; - - /** Elapsed time since boot of startOp event */ - private long mStartElapsedTime; - - /** Id of the client that started the event */ - private @NonNull IBinder mClientId; - - /** The attribution tag for this operation */ - private @Nullable String mAttributionTag; - - /** To call when client dies */ - private @NonNull Runnable mOnDeath; - - /** uidstate used when calling startOp */ - private @AppOpsManager.UidState int mUidState; - - /** Proxy information of the startOp event */ - private @Nullable OpEventProxyInfo mProxy; - - /** Proxy flag information */ - private @OpFlags int mFlags; - - /** How many times the op was started but not finished yet */ - int numUnfinishedStarts; - - /** The attribution flags related to this event */ - private @AttributionFlags int mAttributionFlags; - - /** The id of the attribution chain this even is a part of */ - private int mAttributionChainId; - - /** - * Create a new {@link InProgressStartOpEvent}. - * - * @param startTime The time {@link #startOperation} was called - * @param startElapsedTime The elapsed time when {@link #startOperation} was called - * @param clientId The client id of the caller of {@link #startOperation} - * @param attributionTag The attribution tag for the operation. - * @param onDeath The code to execute on client death - * @param uidState The uidstate of the app {@link #startOperation} was called for - * @param attributionFlags the attribution flags for this operation. - * @param attributionChainId the unique id of the attribution chain this op is a part of. - * @param proxy The proxy information, if {@link #startProxyOperation} was called - * @param flags The trusted/nontrusted/self flags. - * - * @throws RemoteException If the client is dying - */ - private InProgressStartOpEvent(long startTime, long startElapsedTime, - @NonNull IBinder clientId, @Nullable String attributionTag, - @NonNull Runnable onDeath, @AppOpsManager.UidState int uidState, - @Nullable OpEventProxyInfo proxy, @OpFlags int flags, - @AttributionFlags int attributionFlags, int attributionChainId) - throws RemoteException { - mStartTime = startTime; - mStartElapsedTime = startElapsedTime; - mClientId = clientId; - mAttributionTag = attributionTag; - mOnDeath = onDeath; - mUidState = uidState; - mProxy = proxy; - mFlags = flags; - mAttributionFlags = attributionFlags; - mAttributionChainId = attributionChainId; - - clientId.linkToDeath(this, 0); - } - - /** Clean up event */ - public void finish() { - try { - mClientId.unlinkToDeath(this, 0); - } catch (NoSuchElementException e) { - // Either not linked, or already unlinked. Either way, nothing to do. - } - } - - @Override - public void binderDied() { - mOnDeath.run(); - } - - /** - * Reinit existing object with new state. - * - * @param startTime The time {@link #startOperation} was called - * @param startElapsedTime The elapsed time when {@link #startOperation} was called - * @param clientId The client id of the caller of {@link #startOperation} - * @param attributionTag The attribution tag for this operation. - * @param onDeath The code to execute on client death - * @param uidState The uidstate of the app {@link #startOperation} was called for - * @param flags The flags relating to the proxy - * @param proxy The proxy information, if {@link #startProxyOperation} was called - * @param attributionFlags the attribution flags for this operation. - * @param attributionChainId the unique id of the attribution chain this op is a part of. - * @param proxyPool The pool to release previous {@link OpEventProxyInfo} to - * - * @throws RemoteException If the client is dying - */ - public void reinit(long startTime, long startElapsedTime, @NonNull IBinder clientId, - @Nullable String attributionTag, @NonNull Runnable onDeath, - @AppOpsManager.UidState int uidState, @OpFlags int flags, - @Nullable OpEventProxyInfo proxy, @AttributionFlags int attributionFlags, - int attributionChainId, @NonNull Pools.Pool proxyPool - ) throws RemoteException { - mStartTime = startTime; - mStartElapsedTime = startElapsedTime; - mClientId = clientId; - mAttributionTag = attributionTag; - mOnDeath = onDeath; - mUidState = uidState; - mFlags = flags; - - if (mProxy != null) { - proxyPool.release(mProxy); - } - mProxy = proxy; - mAttributionFlags = attributionFlags; - mAttributionChainId = attributionChainId; - - clientId.linkToDeath(this, 0); - } - - /** @return Wall clock time of startOp event */ - public long getStartTime() { - return mStartTime; - } - - /** @return Elapsed time since boot of startOp event */ - public long getStartElapsedTime() { - return mStartElapsedTime; - } - - /** @return Id of the client that started the event */ - public @NonNull IBinder getClientId() { - return mClientId; - } - - /** @return uidstate used when calling startOp */ - public @AppOpsManager.UidState int getUidState() { - return mUidState; - } - - /** @return proxy tag for the access */ - public @Nullable OpEventProxyInfo getProxy() { - return mProxy; - } - - /** @return flags used for the access */ - public @OpFlags int getFlags() { - return mFlags; - } - - /** @return attributoin flags used for the access */ - public @AttributionFlags int getAttributionFlags() { - return mAttributionFlags; - } - - /** @return attribution chain id for the access */ - public int getAttributionChainId() { - return mAttributionChainId; - } - - public void setStartTime(long startTime) { - mStartTime = startTime; - } - - public void setStartElapsedTime(long startElapsedTime) { - mStartElapsedTime = startElapsedTime; - } - } - final class Op { int op; int uid; @@ -5137,10 +4912,11 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch long maxNumStarts = 0; int numInProgressEvents = attributedOp.mInProgressEvents.size(); for (int i = 0; i < numInProgressEvents; i++) { - InProgressStartOpEvent event = attributedOp.mInProgressEvents.valueAt(i); + AttributedOp.InProgressStartOpEvent event = + attributedOp.mInProgressEvents.valueAt(i); earliestElapsedTime = Math.min(earliestElapsedTime, event.getStartElapsedTime()); - maxNumStarts = Math.max(maxNumStarts, event.numUnfinishedStarts); + maxNumStarts = Math.max(maxNumStarts, event.mNumUnfinishedStarts); } pw.print(prefix + "Running start at: "); diff --git a/services/core/java/com/android/server/appop/AttributedOp.java b/services/core/java/com/android/server/appop/AttributedOp.java index c37876bc889f7..dcc36bcf6149c 100644 --- a/services/core/java/com/android/server/appop/AttributedOp.java +++ b/services/core/java/com/android/server/appop/AttributedOp.java @@ -20,6 +20,7 @@ import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_RESUMED; import static android.app.AppOpsManager.makeKey; +import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.AppOpsManager; @@ -29,13 +30,14 @@ import android.os.RemoteException; import android.os.SystemClock; import android.util.ArrayMap; import android.util.LongSparseArray; +import android.util.Pools; import android.util.Slog; -import com.android.internal.annotations.GuardedBy; import com.android.internal.util.function.pooled.PooledLambda; import java.util.ArrayList; import java.util.List; +import java.util.NoSuchElementException; final class AttributedOp { private final @NonNull AppOpsService mAppOpsService; @@ -47,7 +49,8 @@ final class AttributedOp { * *

Key is {@link AppOpsManager#makeKey} */ - @GuardedBy("mAppOpsService") + // TODO(b/248108338) + // @GuardedBy("mAppOpsService") private @Nullable LongSparseArray mAccessEvents; /** @@ -55,7 +58,8 @@ final class AttributedOp { * *

Key is {@link AppOpsManager#makeKey} */ - @GuardedBy("mAppOpsService") + // TODO(b/248108338) + // @GuardedBy("mAppOpsService") private @Nullable LongSparseArray mRejectEvents; /** @@ -63,16 +67,18 @@ final class AttributedOp { * *

Key is clientId */ - @GuardedBy("mAppOpsService") - @Nullable ArrayMap mInProgressEvents; + // TODO(b/248108338) + // @GuardedBy("mAppOpsService") + @Nullable ArrayMap mInProgressEvents; /** * Currently paused startOp events * *

Key is clientId */ - @GuardedBy("mAppOpsService") - @Nullable ArrayMap mPausedInProgressEvents; + // TODO(b/248108338) + // @GuardedBy("mAppOpsService") + @Nullable ArrayMap mPausedInProgressEvents; AttributedOp(@NonNull AppOpsService appOpsService, @Nullable String tag, @NonNull AppOpsService.Op parent) { @@ -224,11 +230,11 @@ final class AttributedOp { } else if (!isStarted && mPausedInProgressEvents == null) { mPausedInProgressEvents = new ArrayMap<>(1); } - ArrayMap events = isStarted + ArrayMap events = isStarted ? mInProgressEvents : mPausedInProgressEvents; long startTime = System.currentTimeMillis(); - AppOpsService.InProgressStartOpEvent event = events.get(clientId); + InProgressStartOpEvent event = events.get(clientId); if (event == null) { event = mAppOpsService.mInProgressStartOpEventPool.acquire(startTime, SystemClock.elapsedRealtime(), clientId, tag, @@ -242,7 +248,7 @@ final class AttributedOp { } } - event.numUnfinishedStarts++; + event.mNumUnfinishedStarts++; if (isStarted) { mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, @@ -277,12 +283,12 @@ final class AttributedOp { return; } - AppOpsService.InProgressStartOpEvent event = mInProgressEvents.valueAt(indexOfToken); + InProgressStartOpEvent event = mInProgressEvents.valueAt(indexOfToken); if (!isPausing) { - event.numUnfinishedStarts--; + event.mNumUnfinishedStarts--; } // If we are pausing, create a NoteOpEvent, but don't change the InProgress event - if (event.numUnfinishedStarts == 0 || isPausing) { + if (event.mNumUnfinishedStarts == 0 || isPausing) { if (!isPausing) { event.finish(); mInProgressEvents.removeAt(indexOfToken); @@ -342,9 +348,9 @@ final class AttributedOp { } // no need to record a paused event finishing. - AppOpsService.InProgressStartOpEvent event = mPausedInProgressEvents.valueAt(indexOfToken); - event.numUnfinishedStarts--; - if (event.numUnfinishedStarts == 0) { + InProgressStartOpEvent event = mPausedInProgressEvents.valueAt(indexOfToken); + event.mNumUnfinishedStarts--; + if (event.mNumUnfinishedStarts == 0) { mPausedInProgressEvents.removeAt(indexOfToken); mAppOpsService.mInProgressStartOpEventPool.release(event); if (mPausedInProgressEvents.isEmpty()) { @@ -378,7 +384,7 @@ final class AttributedOp { } for (int i = 0; i < mInProgressEvents.size(); i++) { - AppOpsService.InProgressStartOpEvent event = mInProgressEvents.valueAt(i); + InProgressStartOpEvent event = mInProgressEvents.valueAt(i); mPausedInProgressEvents.put(event.getClientId(), event); finishOrPause(event.getClientId(), true, true); @@ -406,7 +412,7 @@ final class AttributedOp { long startTime = System.currentTimeMillis(); for (int i = 0; i < mPausedInProgressEvents.size(); i++) { - AppOpsService.InProgressStartOpEvent event = mPausedInProgressEvents.valueAt(i); + InProgressStartOpEvent event = mPausedInProgressEvents.valueAt(i); mInProgressEvents.put(event.getClientId(), event); event.setStartElapsedTime(SystemClock.elapsedRealtime()); event.setStartTime(startTime); @@ -438,11 +444,11 @@ final class AttributedOp { return; } - ArrayMap events = isPaused() + ArrayMap events = isPaused() ? mPausedInProgressEvents : mInProgressEvents; - AppOpsService.InProgressStartOpEvent deadEvent = events.get(clientId); + InProgressStartOpEvent deadEvent = events.get(clientId); if (deadEvent != null) { - deadEvent.numUnfinishedStarts = 1; + deadEvent.mNumUnfinishedStarts = 1; } finished(clientId); @@ -460,20 +466,20 @@ final class AttributedOp { } boolean isRunning = isRunning(); - ArrayMap events = + ArrayMap events = isRunning ? mInProgressEvents : mPausedInProgressEvents; int numInProgressEvents = events.size(); List binders = new ArrayList<>(events.keySet()); for (int i = 0; i < numInProgressEvents; i++) { - AppOpsService.InProgressStartOpEvent event = events.get(binders.get(i)); + InProgressStartOpEvent event = events.get(binders.get(i)); if (event != null && event.getUidState() != newState) { try { // Remove all but one unfinished start count and then call finished() to // remove start event object - int numPreviousUnfinishedStarts = event.numUnfinishedStarts; - event.numUnfinishedStarts = 1; + int numPreviousUnfinishedStarts = event.mNumUnfinishedStarts; + event.mNumUnfinishedStarts = 1; AppOpsManager.OpEventProxyInfo proxy = event.getProxy(); finished(event.getClientId(), false); @@ -492,9 +498,9 @@ final class AttributedOp { } events = isRunning ? mInProgressEvents : mPausedInProgressEvents; - AppOpsService.InProgressStartOpEvent newEvent = events.get(binders.get(i)); + InProgressStartOpEvent newEvent = events.get(binders.get(i)); if (newEvent != null) { - newEvent.numUnfinishedStarts += numPreviousUnfinishedStarts - 1; + newEvent.mNumUnfinishedStarts += numPreviousUnfinishedStarts - 1; } } catch (RemoteException e) { if (AppOpsService.DEBUG) { @@ -546,7 +552,7 @@ final class AttributedOp { @SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService public void add(@NonNull AttributedOp opToAdd) { if (opToAdd.isRunning() || opToAdd.isPaused()) { - ArrayMap ignoredEvents = + ArrayMap ignoredEvents = opToAdd.isRunning() ? opToAdd.mInProgressEvents : opToAdd.mPausedInProgressEvents; Slog.w(AppOpsService.TAG, "Ignoring " + ignoredEvents.size() + " app-ops, running: " @@ -554,7 +560,7 @@ final class AttributedOp { int numInProgressEvents = ignoredEvents.size(); for (int i = 0; i < numInProgressEvents; i++) { - AppOpsService.InProgressStartOpEvent event = ignoredEvents.valueAt(i); + InProgressStartOpEvent event = ignoredEvents.valueAt(i); event.finish(); mAppOpsService.mInProgressStartOpEventPool.release(event); @@ -609,7 +615,7 @@ final class AttributedOp { } for (int i = 0; i < numInProgressEvents; i++) { - AppOpsService.InProgressStartOpEvent event = mInProgressEvents.valueAt(i); + InProgressStartOpEvent event = mInProgressEvents.valueAt(i); accessEvents.append(makeKey(event.getUidState(), event.getFlags()), new AppOpsManager.NoteOpEvent(event.getStartTime(), @@ -623,4 +629,242 @@ final class AttributedOp { return new AppOpsManager.AttributedOpEntry(parent.op, isRunning(), accessEvents, rejectEvents); } + + /** A in progress startOp->finishOp event */ + static final class InProgressStartOpEvent implements IBinder.DeathRecipient { + /** Wall clock time of startOp event (not monotonic) */ + private long mStartTime; + + /** Elapsed time since boot of startOp event */ + private long mStartElapsedTime; + + /** Id of the client that started the event */ + private @NonNull IBinder mClientId; + + /** The attribution tag for this operation */ + private @Nullable String mAttributionTag; + + /** To call when client dies */ + private @NonNull Runnable mOnDeath; + + /** uidstate used when calling startOp */ + private @AppOpsManager.UidState int mUidState; + + /** Proxy information of the startOp event */ + private @Nullable AppOpsManager.OpEventProxyInfo mProxy; + + /** Proxy flag information */ + private @AppOpsManager.OpFlags int mFlags; + + /** How many times the op was started but not finished yet */ + int mNumUnfinishedStarts; + + /** The attribution flags related to this event */ + private @AppOpsManager.AttributionFlags int mAttributionFlags; + + /** The id of the attribution chain this even is a part of */ + private int mAttributionChainId; + + /** + * Create a new {@link InProgressStartOpEvent}. + * + * @param startTime The time {@link #startOperation} was called + * @param startElapsedTime The elapsed time when {@link #startOperation} was called + * @param clientId The client id of the caller of {@link #startOperation} + * @param attributionTag The attribution tag for the operation. + * @param onDeath The code to execute on client death + * @param uidState The uidstate of the app {@link #startOperation} was called for + * @param attributionFlags the attribution flags for this operation. + * @param attributionChainId the unique id of the attribution chain this op is a part of. + * @param proxy The proxy information, if {@link #startProxyOperation} was + * called + * @param flags The trusted/nontrusted/self flags. + * @throws RemoteException If the client is dying + */ + InProgressStartOpEvent(long startTime, long startElapsedTime, + @NonNull IBinder clientId, @Nullable String attributionTag, + @NonNull Runnable onDeath, @AppOpsManager.UidState int uidState, + @Nullable AppOpsManager.OpEventProxyInfo proxy, @AppOpsManager.OpFlags int flags, + @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId) + throws RemoteException { + mStartTime = startTime; + mStartElapsedTime = startElapsedTime; + mClientId = clientId; + mAttributionTag = attributionTag; + mOnDeath = onDeath; + mUidState = uidState; + mProxy = proxy; + mFlags = flags; + mAttributionFlags = attributionFlags; + mAttributionChainId = attributionChainId; + + clientId.linkToDeath(this, 0); + } + + /** Clean up event */ + public void finish() { + try { + mClientId.unlinkToDeath(this, 0); + } catch (NoSuchElementException e) { + // Either not linked, or already unlinked. Either way, nothing to do. + } + } + + @Override + public void binderDied() { + mOnDeath.run(); + } + + /** + * Reinit existing object with new state. + * + * @param startTime The time {@link #startOperation} was called + * @param startElapsedTime The elapsed time when {@link #startOperation} was called + * @param clientId The client id of the caller of {@link #startOperation} + * @param attributionTag The attribution tag for this operation. + * @param onDeath The code to execute on client death + * @param uidState The uidstate of the app {@link #startOperation} was called for + * @param flags The flags relating to the proxy + * @param proxy The proxy information, if {@link #startProxyOperation} + * was called + * @param attributionFlags the attribution flags for this operation. + * @param attributionChainId the unique id of the attribution chain this op is a part of. + * @param proxyPool The pool to release + * previous {@link AppOpsManager.OpEventProxyInfo} to + * @throws RemoteException If the client is dying + */ + public void reinit(long startTime, long startElapsedTime, @NonNull IBinder clientId, + @Nullable String attributionTag, @NonNull Runnable onDeath, + @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags, + @Nullable AppOpsManager.OpEventProxyInfo proxy, + @AppOpsManager.AttributionFlags int attributionFlags, + int attributionChainId, + @NonNull Pools.Pool proxyPool + ) throws RemoteException { + mStartTime = startTime; + mStartElapsedTime = startElapsedTime; + mClientId = clientId; + mAttributionTag = attributionTag; + mOnDeath = onDeath; + mUidState = uidState; + mFlags = flags; + + if (mProxy != null) { + proxyPool.release(mProxy); + } + mProxy = proxy; + mAttributionFlags = attributionFlags; + mAttributionChainId = attributionChainId; + + clientId.linkToDeath(this, 0); + } + + /** @return Wall clock time of startOp event */ + public long getStartTime() { + return mStartTime; + } + + /** @return Elapsed time since boot of startOp event */ + public long getStartElapsedTime() { + return mStartElapsedTime; + } + + /** @return Id of the client that started the event */ + public @NonNull IBinder getClientId() { + return mClientId; + } + + /** @return uidstate used when calling startOp */ + public @AppOpsManager.UidState int getUidState() { + return mUidState; + } + + /** @return proxy tag for the access */ + public @Nullable AppOpsManager.OpEventProxyInfo getProxy() { + return mProxy; + } + + /** @return flags used for the access */ + public @AppOpsManager.OpFlags int getFlags() { + return mFlags; + } + + /** @return attributoin flags used for the access */ + public @AppOpsManager.AttributionFlags int getAttributionFlags() { + return mAttributionFlags; + } + + /** @return attribution chain id for the access */ + public int getAttributionChainId() { + return mAttributionChainId; + } + + public void setStartTime(long startTime) { + mStartTime = startTime; + } + + public void setStartElapsedTime(long startElapsedTime) { + mStartElapsedTime = startElapsedTime; + } + } + + /** + * An unsynchronized pool of {@link InProgressStartOpEvent} objects. + */ + static class InProgressStartOpEventPool extends Pools.SimplePool { + private OpEventProxyInfoPool mOpEventProxyInfoPool; + + InProgressStartOpEventPool(OpEventProxyInfoPool opEventProxyInfoPool, + int maxUnusedPooledObjects) { + super(maxUnusedPooledObjects); + this.mOpEventProxyInfoPool = opEventProxyInfoPool; + } + + InProgressStartOpEvent acquire(long startTime, long elapsedTime, @NonNull IBinder clientId, + @Nullable String attributionTag, @NonNull Runnable onDeath, int proxyUid, + @Nullable String proxyPackageName, @Nullable String proxyAttributionTag, + @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags, + @AppOpsManager.AttributionFlags + int attributionFlags, int attributionChainId) throws RemoteException { + + InProgressStartOpEvent recycled = acquire(); + + AppOpsManager.OpEventProxyInfo proxyInfo = null; + if (proxyUid != Process.INVALID_UID) { + proxyInfo = mOpEventProxyInfoPool.acquire(proxyUid, proxyPackageName, + proxyAttributionTag); + } + + if (recycled != null) { + recycled.reinit(startTime, elapsedTime, clientId, attributionTag, onDeath, + uidState, flags, proxyInfo, attributionFlags, attributionChainId, + mOpEventProxyInfoPool); + return recycled; + } + + return new InProgressStartOpEvent(startTime, elapsedTime, clientId, attributionTag, + onDeath, uidState, proxyInfo, flags, attributionFlags, attributionChainId); + } + } + + /** + * An unsynchronized pool of {@link AppOpsManager.OpEventProxyInfo} objects. + */ + static class OpEventProxyInfoPool extends Pools.SimplePool { + OpEventProxyInfoPool(int maxUnusedPooledObjects) { + super(maxUnusedPooledObjects); + } + + AppOpsManager.OpEventProxyInfo acquire(@IntRange(from = 0) int uid, + @Nullable String packageName, + @Nullable String attributionTag) { + AppOpsManager.OpEventProxyInfo recycled = acquire(); + if (recycled != null) { + recycled.reinit(uid, packageName, attributionTag); + return recycled; + } + + return new AppOpsManager.OpEventProxyInfo(uid, packageName, attributionTag); + } + } }