diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index af73c2bac6f9b..b844fc6b43ab4 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; @@ -71,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; @@ -131,10 +127,7 @@ 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; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; @@ -198,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; @@ -241,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") - private 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") - private final InProgressStartOpEventPool mInProgressStartOpEventPool = - new InProgressStartOpEventPool(); + final AttributedOp.InProgressStartOpEventPool mInProgressStartOpEventPool = + new AttributedOp.InProgressStartOpEventPool(mOpEventProxyInfoPool, + MAX_UNUSED_POOLED_OBJECTS); private final AppOpsManagerInternalImpl mAppOpsManagerInternal = new AppOpsManagerInternalImpl(); @@ -382,60 +382,6 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch return mUidStateTracker; } - /** - * An unsynchronized pool of {@link OpEventProxyInfo} objects. - */ - private 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. - */ - private 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 @@ -652,740 +598,6 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } } - /** A in progress startOp->finishOp event */ - private 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; - } - } - - private final class AttributedOp { - 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") - private @Nullable ArrayMap mInProgressEvents; - - /** - * Currently paused startOp events - * - *

Key is clientId - */ - @GuardedBy("AppOpsService.this") - private @Nullable ArrayMap mPausedInProgressEvents; - - AttributedOp(@Nullable String tag, @NonNull Op parent) { - 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); - - 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 = mOpEventProxyInfoPool.acquire(proxyUid, proxyPackageName, - proxyAttributionTag); - } - - NoteOpEvent existingEvent = mAccessEvents.get(key); - if (existingEvent != null) { - existingEvent.reinit(noteTime, duration, proxyInfo, 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); - - 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, 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) { - 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 = 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) { - onUidStateChanged(uidState); - } - } - - event.numUnfinishedStarts++; - - if (isStarted) { - 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); - - 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); - 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()); - } - } - } - } - } - - // 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); - 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.mClientId, event); - finishOrPause(event.mClientId, true, true); - - 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.mClientId, event); - event.mStartElapsedTime = SystemClock.elapsedRealtime(); - event.mStartTime = startTime; - mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, - parent.packageName, tag, event.mUidState, event.mFlags, startTime, - event.getAttributionFlags(), event.getAttributionChainId()); - if (shouldSendActive) { - 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, - 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 (AppOpsService.this) { - 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(); - 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; @@ -1425,7 +637,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 +887,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 +1128,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 +3228,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 +3281,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) { @@ -5700,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 new file mode 100644 index 0000000000000..dcc36bcf6149c --- /dev/null +++ b/services/core/java/com/android/server/appop/AttributedOp.java @@ -0,0 +1,870 @@ +/* + * 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.IntRange; +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.Pools; +import android.util.Slog; + +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; + 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} + */ + // TODO(b/248108338) + // @GuardedBy("mAppOpsService") + private @Nullable LongSparseArray mAccessEvents; + + /** + * Last rejected accesses for each uidState/opFlag combination + * + *

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

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

Key is clientId + */ + // TODO(b/248108338) + // @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(); + 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.mNumUnfinishedStarts++; + + 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; + } + + InProgressStartOpEvent event = mInProgressEvents.valueAt(indexOfToken); + if (!isPausing) { + event.mNumUnfinishedStarts--; + } + // If we are pausing, create a NoteOpEvent, but don't change the InProgress event + if (event.mNumUnfinishedStarts == 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. + InProgressStartOpEvent event = mPausedInProgressEvents.valueAt(indexOfToken); + event.mNumUnfinishedStarts--; + if (event.mNumUnfinishedStarts == 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++) { + 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.mNumUnfinishedStarts = 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.mNumUnfinishedStarts; + event.mNumUnfinishedStarts = 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; + InProgressStartOpEvent newEvent = events.get(binders.get(i)); + if (newEvent != null) { + newEvent.mNumUnfinishedStarts += 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++) { + 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++) { + 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); + } + + /** 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); + } + } +}