diff options
6 files changed, 1141 insertions, 397 deletions
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index bc59d60bebd1..6d907f3cf201 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -554,7 +554,7 @@ public class AppOpsManager { * @hide */ public static int resolveFirstUnrestrictedUidState(int op) { - return UID_STATE_FOREGROUND; + return UID_STATE_MAX_LAST_NON_RESTRICTED; } /** diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 7bdcfdbd8ed7..f3397c3a707f 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -16,9 +16,6 @@ package com.android.server.appop; -import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_CAMERA; -import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION; -import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_MICROPHONE; import static android.app.AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE; import static android.app.AppOpsManager.ATTRIBUTION_FLAG_TRUSTED; import static android.app.AppOpsManager.CALL_BACK_ON_SWITCHED_OP; @@ -56,13 +53,6 @@ import static android.app.AppOpsManager.SAMPLING_STRATEGY_RARELY_USED; import static android.app.AppOpsManager.SAMPLING_STRATEGY_UNIFORM; import static android.app.AppOpsManager.SAMPLING_STRATEGY_UNIFORM_OPS; import static android.app.AppOpsManager.SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE; -import static android.app.AppOpsManager.UID_STATE_BACKGROUND; -import static android.app.AppOpsManager.UID_STATE_CACHED; -import static android.app.AppOpsManager.UID_STATE_FOREGROUND; -import static android.app.AppOpsManager.UID_STATE_FOREGROUND_SERVICE; -import static android.app.AppOpsManager.UID_STATE_MAX_LAST_NON_RESTRICTED; -import static android.app.AppOpsManager.UID_STATE_PERSISTENT; -import static android.app.AppOpsManager.UID_STATE_TOP; import static android.app.AppOpsManager._NUM_OP; import static android.app.AppOpsManager.extractFlagsFromKey; import static android.app.AppOpsManager.extractUidStateFromKey; @@ -72,7 +62,6 @@ import static android.app.AppOpsManager.opAllowSystemBypassRestriction; import static android.app.AppOpsManager.opRestrictsRead; import static android.app.AppOpsManager.opToName; import static android.app.AppOpsManager.opToPublicName; -import static android.app.AppOpsManager.resolveFirstUnrestrictedUidState; import static android.content.Intent.ACTION_PACKAGE_REMOVED; import static android.content.Intent.EXTRA_REPLACING; import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS; @@ -80,8 +69,6 @@ import static android.content.pm.PermissionInfo.PROTECTION_FLAG_APPOP; import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS; -import static java.lang.Long.max; - import android.Manifest; import android.annotation.IntRange; import android.annotation.NonNull; @@ -167,6 +154,7 @@ import com.android.internal.app.IAppOpsService; import com.android.internal.app.IAppOpsStartedCallback; import com.android.internal.app.MessageSamplingConfig; import com.android.internal.compat.IPlatformCompat; +import com.android.internal.os.Clock; import com.android.internal.util.ArrayUtils; import com.android.internal.util.DumpUtils; import com.android.internal.util.Preconditions; @@ -236,31 +224,6 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch // Constant meaning that any UID should be matched when dispatching callbacks private static final int UID_ANY = -2; - // Map from process states to the uid states we track. - private static final int[] PROCESS_STATE_TO_UID_STATE = new int[] { - UID_STATE_PERSISTENT, // ActivityManager.PROCESS_STATE_PERSISTENT - UID_STATE_PERSISTENT, // ActivityManager.PROCESS_STATE_PERSISTENT_UI - UID_STATE_TOP, // ActivityManager.PROCESS_STATE_TOP - UID_STATE_FOREGROUND, // ActivityManager.PROCESS_STATE_BOUND_TOP - UID_STATE_FOREGROUND_SERVICE, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE - UID_STATE_FOREGROUND, // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE - UID_STATE_BACKGROUND, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND - UID_STATE_BACKGROUND, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND - UID_STATE_BACKGROUND, // ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND - UID_STATE_BACKGROUND, // ActivityManager.PROCESS_STATE_BACKUP - UID_STATE_BACKGROUND, // ActivityManager.PROCESS_STATE_SERVICE - UID_STATE_BACKGROUND, // ActivityManager.PROCESS_STATE_RECEIVER - UID_STATE_CACHED, // ActivityManager.PROCESS_STATE_TOP_SLEEPING - UID_STATE_CACHED, // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT - UID_STATE_CACHED, // ActivityManager.PROCESS_STATE_HOME - UID_STATE_CACHED, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY - UID_STATE_CACHED, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY - UID_STATE_CACHED, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT - UID_STATE_CACHED, // ActivityManager.PROCESS_STATE_CACHED_RECENT - UID_STATE_CACHED, // ActivityManager.PROCESS_STATE_CACHED_EMPTY - UID_STATE_CACHED, // ActivityManager.PROCESS_STATE_NONEXISTENT - }; - private static final int[] OPS_RESTRICTED_ON_SUSPEND = { OP_PLAY_AUDIO, OP_RECORD_AUDIO, @@ -275,6 +238,13 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch private static final int MAX_UNUSED_POOLED_OBJECTS = 3; private static final int RARELY_USED_PACKAGES_INITIALIZATION_DELAY_MILLIS = 300000; + /* + * TODO b/246429313 + * android.app.appops.cts.AppOpEventCollectionTest#switchUidStateWhileOpsAreRunning breaks when + * set to true + */ + private static final boolean USE_DELAYED_UID_STATE_CHANGES_FOR_ATTR_OP_UPDATES = false; + final Context mContext; final AtomicFile mFile; private final @Nullable File mNoteOpCallerStacktracesFile; @@ -346,8 +316,6 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch volatile @NonNull HistoricalRegistry mHistoricalRegistry = new HistoricalRegistry(this); - long mLastRealtime; - /* * These are app op restrictions imposed per user from various parties. */ @@ -408,6 +376,21 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch /** Interface for app-op modes.*/ @VisibleForTesting AppOpsServiceInterface mAppOpsServiceInterface; + private AppOpsUidStateTracker mUidStateTracker; + + /** Hands the definition of foreground and uid states */ + @GuardedBy("this") + public AppOpsUidStateTracker getUidStateTracker() { + if (mUidStateTracker == null) { + mUidStateTracker = new AppOpsUidStateTrackerImpl( + LocalServices.getService(ActivityManagerInternal.class), mHandler, + Clock.SYSTEM_CLOCK, mConstants); + + mUidStateTracker.addUidStateChangedCallback(mHandler, this::onUidStateChanged); + } + return mUidStateTracker; + } + /** * An unsynchronized pool of {@link OpEventProxyInfo} objects. */ @@ -467,7 +450,6 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch * global Settings. Any access to this class or its fields should be done while * holding the AppOpsService lock. */ - @VisibleForTesting final class Constants extends ContentObserver { /** @@ -555,14 +537,6 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch final class UidState { public final int uid; - public int state = UID_STATE_CACHED; - public int pendingState = UID_STATE_CACHED; - public long pendingStateCommitTime; - public int capability; - public int pendingCapability; - public boolean appWidgetVisible; - public boolean pendingAppWidgetVisible; - public ArrayMap<String, Ops> pkgOps; // true indicates there is an interested observer, false there isn't but it has such an op @@ -596,10 +570,8 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } } return (pkgOps == null || pkgOps.isEmpty()) - && (state == UID_STATE_CACHED - && (pendingState == UID_STATE_CACHED)) - && (mAppOpsServiceInterface.areUidModesDefault(uid) - && areAllPackageModesDefault); + && mAppOpsServiceInterface.areUidModesDefault(uid) + && areAllPackageModesDefault; } // Functions for uid mode access and manipulation. @@ -615,52 +587,9 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch return mAppOpsServiceInterface.setUidMode(uid, op, mode); } + @SuppressWarnings("GuardedBy") int evalMode(int op, int mode) { - if (mode == MODE_FOREGROUND) { - if (appWidgetVisible) { - return MODE_ALLOWED; - } else if (mActivityManagerInternal != null - && mActivityManagerInternal.isPendingTopUid(uid)) { - return MODE_ALLOWED; - } else if (mActivityManagerInternal != null - && mActivityManagerInternal.isTempAllowlistedForFgsWhileInUse(uid)) { - return MODE_ALLOWED; - } else if (state <= UID_STATE_TOP) { - // process is in TOP. - return MODE_ALLOWED; - } else if (state <= AppOpsManager.resolveFirstUnrestrictedUidState(op)) { - // process is in foreground, check its capability. - switch (op) { - case AppOpsManager.OP_FINE_LOCATION: - case AppOpsManager.OP_COARSE_LOCATION: - case AppOpsManager.OP_MONITOR_LOCATION: - case AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION: - if ((capability & PROCESS_CAPABILITY_FOREGROUND_LOCATION) != 0) { - return MODE_ALLOWED; - } else { - return MODE_IGNORED; - } - case OP_CAMERA: - if ((capability & PROCESS_CAPABILITY_FOREGROUND_CAMERA) != 0) { - return MODE_ALLOWED; - } else { - return MODE_IGNORED; - } - case OP_RECORD_AUDIO: - if ((capability & PROCESS_CAPABILITY_FOREGROUND_MICROPHONE) != 0) { - return MODE_ALLOWED; - } else { - return MODE_IGNORED; - } - default: - return MODE_ALLOWED; - } - } else { - // process is not in foreground. - return MODE_IGNORED; - } - } - return mode; + return getUidStateTracker().evalMode(uid, op, mode); } public void evalForegroundOps() { @@ -683,6 +612,16 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } } } + + @SuppressWarnings("GuardedBy") + public int getState() { + return getUidStateTracker().getUidState(uid); + } + + @SuppressWarnings("GuardedBy") + public void dump(PrintWriter pw, long nowElapsed) { + getUidStateTracker().dumpUidState(pw, uid, nowElapsed); + } } final static class Ops extends SparseArray<Op> { @@ -710,7 +649,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } } - /** Returned from {@link #verifyAndGetBypass(int, String, String, String, boolean)}. */ + /** Returned from {@link #verifyAndGetBypass(int, String, String, String)}. */ private static final class PackageVerificationResult { final RestrictionBypass bypass; @@ -1481,10 +1420,6 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch UserHandle.getUserId(this.uid)); } - int evalMode() { - return uidState.evalMode(op, getMode()); - } - void removeAttributionsWithNoTime() { for (int i = mAttributions.size() - 1; i >= 0; i--) { if (!mAttributions.valueAt(i).hasAnyTime()) { @@ -2096,59 +2031,99 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } } - /** - * Update the pending state for the uid - * - * @param currentTime The current elapsed real time - * @param uid The uid that has a pending state - */ - private void updatePendingState(long currentTime, int uid) { + // The callback method from ForegroundPolicyInterface + private void onUidStateChanged(int uid, int state, boolean foregroundModeMayChange) { synchronized (this) { - mLastRealtime = max(currentTime, mLastRealtime); - updatePendingStateIfNeededLocked(mUidStates.get(uid)); + UidState uidState = getUidStateLocked(uid, true); + + if (uidState != null && foregroundModeMayChange && uidState.hasForegroundWatchers) { + for (int fgi = uidState.foregroundOps.size() - 1; fgi >= 0; fgi--) { + if (!uidState.foregroundOps.valueAt(fgi)) { + continue; + } + final int code = uidState.foregroundOps.keyAt(fgi); + + if (uidState.getUidMode(code) != AppOpsManager.opToDefaultMode(code) + && uidState.getUidMode(code) == AppOpsManager.MODE_FOREGROUND) { + mHandler.sendMessage(PooledLambda.obtainMessage( + AppOpsService::notifyOpChangedForAllPkgsInUid, + this, code, uidState.uid, true, null)); + } else if (uidState.pkgOps != null) { + final ArraySet<OnOpModeChangedListener> listenerSet = + mAppOpsServiceInterface.getOpModeChangedListeners(code); + if (listenerSet != null) { + for (int cbi = listenerSet.size() - 1; cbi >= 0; cbi--) { + final OnOpModeChangedListener listener = listenerSet.valueAt(cbi); + if ((listener.getFlags() + & AppOpsManager.WATCH_FOREGROUND_CHANGES) == 0 + || !listener.isWatchingUid(uidState.uid)) { + continue; + } + for (int pkgi = uidState.pkgOps.size() - 1; pkgi >= 0; pkgi--) { + final Op op = uidState.pkgOps.valueAt(pkgi).get(code); + if (op == null) { + continue; + } + if (op.getMode() == AppOpsManager.MODE_FOREGROUND) { + mHandler.sendMessage(PooledLambda.obtainMessage( + AppOpsService::notifyOpChanged, + this, listenerSet.valueAt(cbi), code, uidState.uid, + uidState.pkgOps.keyAt(pkgi))); + } + } + } + } + } + } + } + + if (USE_DELAYED_UID_STATE_CHANGES_FOR_ATTR_OP_UPDATES) { + // Proposed behavior change, previously there were two rounds of UID state change + // operations. It seems like there is no need for + // attributedOp.onUidStateChanged(state) to update before settle time is reached, + // which if true then this is the preferred route. + if (uidState != null && uidState.pkgOps != null) { + int numPkgs = uidState.pkgOps.size(); + for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) { + Ops ops = uidState.pkgOps.valueAt(pkgNum); + + int numOps = ops.size(); + for (int opNum = 0; opNum < numOps; opNum++) { + Op op = ops.valueAt(opNum); + + int numAttributions = op.mAttributions.size(); + for (int attributionNum = 0; attributionNum < numAttributions; + attributionNum++) { + AttributedOp attributedOp = op.mAttributions.valueAt( + attributionNum); + + attributedOp.onUidStateChanged(state); + } + } + } + } + } } } + /** + * Notify the proc state or capability has changed for a certain UID. + */ public void updateUidProcState(int uid, int procState, @ActivityManager.ProcessCapability int capability) { synchronized (this) { - final UidState uidState = getUidStateLocked(uid, true); - final int newState = PROCESS_STATE_TO_UID_STATE[procState]; - if (uidState != null && (uidState.pendingState != newState - || uidState.pendingCapability != capability)) { - final int oldPendingState = uidState.pendingState; - uidState.pendingState = newState; - uidState.pendingCapability = capability; - if (newState < uidState.state - || (newState <= UID_STATE_MAX_LAST_NON_RESTRICTED - && uidState.state > UID_STATE_MAX_LAST_NON_RESTRICTED)) { - // We are moving to a more important state, or the new state may be in the - // foreground and the old state is in the background, then always do it - // immediately. - commitUidPendingStateLocked(uidState); - } else if (newState == uidState.state && capability != uidState.capability) { - // No change on process state, but process capability has changed. - commitUidPendingStateLocked(uidState); - } else if (uidState.pendingStateCommitTime == 0) { - // We are moving to a less important state for the first time, - // delay the application for a bit. - final long settleTime; - if (uidState.state <= UID_STATE_TOP) { - settleTime = mConstants.TOP_STATE_SETTLE_TIME; - } else if (uidState.state <= UID_STATE_FOREGROUND_SERVICE) { - settleTime = mConstants.FG_SERVICE_STATE_SETTLE_TIME; - } else { - settleTime = mConstants.BG_STATE_SETTLE_TIME; - } - final long commitTime = SystemClock.elapsedRealtime() + settleTime; - uidState.pendingStateCommitTime = commitTime; + getUidStateTracker().updateUidProcState(uid, procState, capability); + if (!mUidStates.contains(uid)) { + UidState uidState = new UidState(uid); + mUidStates.put(uid, uidState); + onUidStateChanged(uid, + AppOpsUidStateTracker.processStateToUidState(procState), false); + } - mHandler.sendMessageDelayed( - PooledLambda.obtainMessage(AppOpsService::updatePendingState, this, - commitTime + 1, uid), settleTime + 1); - } + if (!USE_DELAYED_UID_STATE_CHANGES_FOR_ATTR_OP_UPDATES) { + UidState uidState = mUidStates.get(uid); - if (uidState.pkgOps != null) { + if (uidState != null && uidState.pkgOps != null) { int numPkgs = uidState.pkgOps.size(); for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) { Ops ops = uidState.pkgOps.valueAt(pkgNum); @@ -2163,7 +2138,9 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch AttributedOp attributedOp = op.mAttributions.valueAt( attributionNum); - attributedOp.onUidStateChanged(newState); + attributedOp.onUidStateChanged( + AppOpsUidStateTracker.processStateToUidState( + procState)); } } } @@ -3162,7 +3139,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch if (op == null) { return AppOpsManager.opToDefaultMode(code); } - return raw ? op.getMode() : op.evalMode(); + return raw ? op.getMode() : op.uidState.evalMode(op.op, op.getMode()); } } @@ -3380,7 +3357,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch final int switchCode = AppOpsManager.opToSwitch(code); final UidState uidState = ops.uidState; if (isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass, false)) { - attributedOp.rejected(uidState.state, flags); + attributedOp.rejected(uidState.getState(), flags); scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags, AppOpsManager.MODE_IGNORED); return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag, @@ -3394,7 +3371,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch if (DEBUG) Slog.d(TAG, "noteOperation: uid reject #" + uidMode + " for code " + switchCode + " (" + code + ") uid " + uid + " package " + packageName + " flags: " + AppOpsManager.flagsToString(flags)); - attributedOp.rejected(uidState.state, flags); + attributedOp.rejected(uidState.getState(), flags); scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags, uidMode); return new SyncNotedAppOp(uidMode, code, attributionTag, packageName); @@ -3402,12 +3379,12 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } else { final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, uid, true) : op; - final int mode = switchOp.evalMode(); + final int mode = switchOp.uidState.evalMode(switchOp.op, switchOp.getMode()); if (mode != AppOpsManager.MODE_ALLOWED) { if (DEBUG) Slog.d(TAG, "noteOperation: reject #" + mode + " for code " + switchCode + " (" + code + ") uid " + uid + " package " + packageName + " flags: " + AppOpsManager.flagsToString(flags)); - attributedOp.rejected(uidState.state, flags); + attributedOp.rejected(uidState.getState(), flags); scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags, mode); return new SyncNotedAppOp(mode, code, attributionTag, packageName); @@ -3422,7 +3399,8 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags, AppOpsManager.MODE_ALLOWED); - attributedOp.accessed(proxyUid, proxyPackageName, proxyAttributionTag, uidState.state, + attributedOp.accessed(proxyUid, proxyPackageName, proxyAttributionTag, + uidState.getState(), flags); if (shouldCollectAsyncNotedOp) { @@ -3913,7 +3891,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch + packageName + " flags: " + AppOpsManager.flagsToString(flags)); } if (!dryRun) { - attributedOp.rejected(uidState.state, flags); + attributedOp.rejected(uidState.getState(), flags); scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, flags, uidMode, startType, attributionFlags, attributionChainId); } @@ -3922,14 +3900,14 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } else { final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, uid, true) : op; - final int mode = switchOp.evalMode(); + final int mode = switchOp.uidState.evalMode(switchOp.op, switchOp.getMode()); if (mode != AppOpsManager.MODE_ALLOWED && (!startIfModeDefault || mode != MODE_DEFAULT)) { if (DEBUG) Slog.d(TAG, "startOperation: reject #" + mode + " for code " + switchCode + " (" + code + ") uid " + uid + " package " + packageName + " flags: " + AppOpsManager.flagsToString(flags)); if (!dryRun) { - attributedOp.rejected(uidState.state, flags); + attributedOp.rejected(uidState.getState(), flags); scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, flags, mode, startType, attributionFlags, attributionChainId); } @@ -3943,12 +3921,12 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch try { if (isRestricted) { attributedOp.createPaused(clientId, proxyUid, proxyPackageName, - proxyAttributionTag, uidState.state, flags, attributionFlags, - attributionChainId); + proxyAttributionTag, uidState.getState(), flags, + attributionFlags, attributionChainId); } else { attributedOp.started(clientId, proxyUid, proxyPackageName, - proxyAttributionTag, uidState.state, flags, attributionFlags, - attributionChainId); + proxyAttributionTag, uidState.getState(), flags, + attributionFlags, attributionChainId); startType = START_TYPE_STARTED; } } catch (RemoteException e) { @@ -4362,101 +4340,14 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } uidState = new UidState(uid); mUidStates.put(uid, uidState); - } else { - updatePendingStateIfNeededLocked(uidState); } - return uidState; - } - /** - * Check if the pending state should be updated and do so if needed - * - * @param uidState The uidState that might have a pending state - */ - private void updatePendingStateIfNeededLocked(@NonNull UidState uidState) { - if (uidState != null) { - if (uidState.pendingStateCommitTime != 0) { - if (uidState.pendingStateCommitTime < mLastRealtime) { - commitUidPendingStateLocked(uidState); - } else { - mLastRealtime = SystemClock.elapsedRealtime(); - if (uidState.pendingStateCommitTime < mLastRealtime) { - commitUidPendingStateLocked(uidState); - } - } - } - } - } - - private void commitUidPendingStateLocked(UidState uidState) { - if (uidState.hasForegroundWatchers) { - for (int fgi = uidState.foregroundOps.size() - 1; fgi >= 0; fgi--) { - if (!uidState.foregroundOps.valueAt(fgi)) { - continue; - } - final int code = uidState.foregroundOps.keyAt(fgi); - // For location ops we consider fg state only if the fg service - // is of location type, for all other ops any fg service will do. - final long firstUnrestrictedUidState = resolveFirstUnrestrictedUidState(code); - final boolean resolvedLastFg = uidState.state <= firstUnrestrictedUidState; - final boolean resolvedNowFg = uidState.pendingState <= firstUnrestrictedUidState; - if (resolvedLastFg == resolvedNowFg - && uidState.capability == uidState.pendingCapability - && uidState.appWidgetVisible == uidState.pendingAppWidgetVisible) { - continue; - } - - if (uidState.getUidMode(code) != AppOpsManager.opToDefaultMode(code) - && uidState.getUidMode(code) == AppOpsManager.MODE_FOREGROUND) { - mHandler.sendMessage(PooledLambda.obtainMessage( - AppOpsService::notifyOpChangedForAllPkgsInUid, - this, code, uidState.uid, true, null)); - } else if (uidState.pkgOps != null) { - final ArraySet<OnOpModeChangedListener> listenerSet = - mAppOpsServiceInterface.getOpModeChangedListeners(code); - if (listenerSet != null) { - for (int cbi = listenerSet.size() - 1; cbi >= 0; cbi--) { - final OnOpModeChangedListener listener = listenerSet.valueAt(cbi); - if ((listener.getFlags() - & AppOpsManager.WATCH_FOREGROUND_CHANGES) == 0 - || !listener.isWatchingUid(uidState.uid)) { - continue; - } - for (int pkgi = uidState.pkgOps.size() - 1; pkgi >= 0; pkgi--) { - final Op op = uidState.pkgOps.valueAt(pkgi).get(code); - if (op == null) { - continue; - } - if (op.getMode() == AppOpsManager.MODE_FOREGROUND) { - mHandler.sendMessage(PooledLambda.obtainMessage( - AppOpsService::notifyOpChanged, - this, listenerSet.valueAt(cbi), code, uidState.uid, - uidState.pkgOps.keyAt(pkgi))); - } - } - } - } - } - } - } - uidState.state = uidState.pendingState; - uidState.capability = uidState.pendingCapability; - uidState.appWidgetVisible = uidState.pendingAppWidgetVisible; - uidState.pendingStateCommitTime = 0; + return uidState; } private void updateAppWidgetVisibility(SparseArray<String> uidPackageNames, boolean visible) { synchronized (this) { - for (int i = uidPackageNames.size() - 1; i >= 0; i--) { - final int uid = uidPackageNames.keyAt(i); - final UidState uidState = getUidStateLocked(uid, true); - if (uidState != null && (uidState.pendingAppWidgetVisible != visible)) { - uidState.pendingAppWidgetVisible = visible; - if (uidState.pendingAppWidgetVisible != uidState.appWidgetVisible) { - commitUidPendingStateLocked(uidState); - } - } - } + getUidStateTracker().updateAppWidgetVisibility(uidPackageNames, visible); } } @@ -6207,31 +6098,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } pw.print(" Uid "); UserHandle.formatUid(pw, uidState.uid); pw.println(":"); - pw.print(" state="); - pw.println(AppOpsManager.getUidStateName(uidState.state)); - if (uidState.state != uidState.pendingState) { - pw.print(" pendingState="); - pw.println(AppOpsManager.getUidStateName(uidState.pendingState)); - } - pw.print(" capability="); - ActivityManager.printCapabilitiesFull(pw, uidState.capability); - pw.println(); - if (uidState.capability != uidState.pendingCapability) { - pw.print(" pendingCapability="); - ActivityManager.printCapabilitiesFull(pw, uidState.pendingCapability); - pw.println(); - } - pw.print(" appWidgetVisible="); - pw.println(uidState.appWidgetVisible); - if (uidState.appWidgetVisible != uidState.pendingAppWidgetVisible) { - pw.print(" pendingAppWidgetVisible="); - pw.println(uidState.pendingAppWidgetVisible); - } - if (uidState.pendingStateCommitTime != 0) { - pw.print(" pendingStateCommitTime="); - TimeUtils.formatDuration(uidState.pendingStateCommitTime, nowElapsed, pw); - pw.println(); - } + uidState.dump(pw, nowElapsed); if (uidState.foregroundOps != null && (dumpMode < 0 || dumpMode == AppOpsManager.MODE_FOREGROUND)) { pw.println(" foregroundOps:"); diff --git a/services/core/java/com/android/server/appop/AppOpsUidStateTracker.java b/services/core/java/com/android/server/appop/AppOpsUidStateTracker.java new file mode 100644 index 000000000000..f3c6c75df382 --- /dev/null +++ b/services/core/java/com/android/server/appop/AppOpsUidStateTracker.java @@ -0,0 +1,118 @@ +/* + * 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.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE; +import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP; +import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; +import static android.app.ActivityManager.PROCESS_STATE_PERSISTENT_UI; +import static android.app.ActivityManager.PROCESS_STATE_RECEIVER; +import static android.app.ActivityManager.PROCESS_STATE_TOP; +import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN; +import static android.app.AppOpsManager.UID_STATE_BACKGROUND; +import static android.app.AppOpsManager.UID_STATE_CACHED; +import static android.app.AppOpsManager.UID_STATE_FOREGROUND; +import static android.app.AppOpsManager.UID_STATE_FOREGROUND_SERVICE; +import static android.app.AppOpsManager.UID_STATE_PERSISTENT; +import static android.app.AppOpsManager.UID_STATE_TOP; + +import android.os.Handler; +import android.util.SparseArray; + +import java.io.PrintWriter; + +interface AppOpsUidStateTracker { + + // Map from process states to the uid states we track. + static int processStateToUidState(int procState) { + if (procState == PROCESS_STATE_UNKNOWN) { + return UID_STATE_CACHED; + } + + if (procState <= PROCESS_STATE_PERSISTENT_UI) { + return UID_STATE_PERSISTENT; + } + + if (procState <= PROCESS_STATE_TOP) { + return UID_STATE_TOP; + } + + if (procState <= PROCESS_STATE_BOUND_TOP) { + return UID_STATE_FOREGROUND; + } + + if (procState <= PROCESS_STATE_FOREGROUND_SERVICE) { + return UID_STATE_FOREGROUND_SERVICE; + } + + if (procState <= PROCESS_STATE_BOUND_FOREGROUND_SERVICE) { + return UID_STATE_FOREGROUND; + } + + if (procState <= PROCESS_STATE_RECEIVER) { + return UID_STATE_BACKGROUND; + } + + return UID_STATE_CACHED; + } + + /* + * begin data pushed from appopsservice + */ + + void updateUidProcState(int uid, int procState, int capability); + + void updateAppWidgetVisibility(SparseArray<String> uidPackageNames, boolean visible); + + /* + * end data pushed from appopsservice + */ + + /** + * Gets the {@link android.app.AppOpsManager.UidState} that the uid current is in. + */ + int getUidState(int uid); + + /** + * Given a uid, code, and mode, resolve any foregroundness to MODE_IGNORED or MODE_ALLOWED + */ + int evalMode(int uid, int code, int mode); + + /** + * Listen to changes in {@link android.app.AppOpsManager.UidState} + */ + void addUidStateChangedCallback(Handler handler, + UidStateChangedCallback callback); + + /** + * Remove a {@link UidStateChangedCallback} + */ + void removeUidStateChangedCallback(UidStateChangedCallback callback); + + interface UidStateChangedCallback { + /** + * Invoked when a UID's {@link android.app.AppOpsManager.UidState} changes. + * @param uid The uid that changed. + * @param uidState The state that was changed to. + * @param foregroundModeMayChange True if there may be a op in MODE_FOREGROUND whose + * evaluated result may have changed. + */ + void onUidStateChanged(int uid, int uidState, boolean foregroundModeMayChange); + } + + void dumpUidState(PrintWriter pw, int uid, long nowElapsed); +} diff --git a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java new file mode 100644 index 000000000000..95b420aa683b --- /dev/null +++ b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java @@ -0,0 +1,332 @@ +/* + * 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.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_CAMERA; +import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION; +import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_MICROPHONE; +import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE; +import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT; +import static android.app.ActivityManager.ProcessCapability; +import static android.app.AppOpsManager.MODE_ALLOWED; +import static android.app.AppOpsManager.MODE_IGNORED; +import static android.app.AppOpsManager.OP_CAMERA; +import static android.app.AppOpsManager.OP_RECORD_AUDIO; +import static android.app.AppOpsManager.UID_STATE_CACHED; +import static android.app.AppOpsManager.UID_STATE_FOREGROUND_SERVICE; +import static android.app.AppOpsManager.UID_STATE_MAX_LAST_NON_RESTRICTED; +import static android.app.AppOpsManager.UID_STATE_TOP; + +import static com.android.server.appop.AppOpsUidStateTracker.processStateToUidState; + +import android.app.ActivityManager; +import android.app.ActivityManagerInternal; +import android.app.AppOpsManager; +import android.os.Handler; +import android.util.ArrayMap; +import android.util.SparseArray; +import android.util.SparseBooleanArray; +import android.util.SparseIntArray; +import android.util.SparseLongArray; +import android.util.TimeUtils; + +import com.android.internal.os.Clock; +import com.android.internal.util.function.pooled.PooledLambda; + +import java.io.PrintWriter; + +class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { + + private static final String LOG_TAG = AppOpsUidStateTrackerImpl.class.getSimpleName(); + + private final Handler mHandler; + private final Clock mClock; + private ActivityManagerInternal mActivityManagerInternal; + private AppOpsService.Constants mConstants; + + private SparseIntArray mUidStates = new SparseIntArray(); + private SparseIntArray mPendingUidStates = new SparseIntArray(); + private SparseIntArray mCapability = new SparseIntArray(); + private SparseIntArray mPendingCapability = new SparseIntArray(); + private SparseBooleanArray mVisibleAppWidget = new SparseBooleanArray(); + private SparseBooleanArray mPendingVisibleAppWidget = new SparseBooleanArray(); + private SparseLongArray mPendingCommitTime = new SparseLongArray(); + + private ArrayMap<UidStateChangedCallback, Handler> mUidStateChangedCallbacks = new ArrayMap<>(); + + AppOpsUidStateTrackerImpl(ActivityManagerInternal activityManagerInternal, + Handler handler, Clock clock, AppOpsService.Constants constants) { + mActivityManagerInternal = activityManagerInternal; + mHandler = handler; + mClock = clock; + mConstants = constants; + } + + @Override + public int getUidState(int uid) { + return getUidStateLocked(uid); + } + + private int getUidStateLocked(int uid) { + updateUidPendingStateIfNeeded(uid); + return mUidStates.get(uid, UID_STATE_CACHED); + } + + @Override + public int evalMode(int uid, int code, int mode) { + if (mode != AppOpsManager.MODE_FOREGROUND) { + return mode; + } + + int uidStateValue; + int capability; + boolean visibleAppWidget; + boolean pendingTop; + boolean tempAllowlist; + uidStateValue = getUidState(uid); + capability = getUidCapability(uid); + visibleAppWidget = getUidVisibleAppWidget(uid); + pendingTop = mActivityManagerInternal.isPendingTopUid(uid); + tempAllowlist = mActivityManagerInternal.isTempAllowlistedForFgsWhileInUse(uid); + + return evalMode(uidStateValue, code, mode, capability, visibleAppWidget, pendingTop, + tempAllowlist); + } + + private static int evalMode(int uidState, int code, int mode, int capability, + boolean appWidgetVisible, boolean pendingTop, boolean tempAllowlist) { + if (mode != AppOpsManager.MODE_FOREGROUND) { + return mode; + } + + if (appWidgetVisible || pendingTop || tempAllowlist) { + return MODE_ALLOWED; + } + + switch (code) { + case AppOpsManager.OP_FINE_LOCATION: + case AppOpsManager.OP_COARSE_LOCATION: + case AppOpsManager.OP_MONITOR_LOCATION: + case AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION: + if ((capability & PROCESS_CAPABILITY_FOREGROUND_LOCATION) == 0) { + return MODE_IGNORED; + } else { + return MODE_ALLOWED; + } + case OP_CAMERA: + if ((capability & PROCESS_CAPABILITY_FOREGROUND_CAMERA) == 0) { + return MODE_IGNORED; + } else { + return MODE_ALLOWED; + } + case OP_RECORD_AUDIO: + if ((capability & PROCESS_CAPABILITY_FOREGROUND_MICROPHONE) == 0) { + return MODE_IGNORED; + } else { + return MODE_ALLOWED; + } + } + + if (uidState > AppOpsManager.resolveFirstUnrestrictedUidState(code)) { + return MODE_IGNORED; + } + + return MODE_ALLOWED; + } + + @Override + public void addUidStateChangedCallback(Handler handler, UidStateChangedCallback callback) { + if (mUidStateChangedCallbacks.containsKey(callback)) { + throw new IllegalStateException("Callback is already registered."); + } + mUidStateChangedCallbacks.put(callback, handler); + } + + @Override + public void removeUidStateChangedCallback(UidStateChangedCallback callback) { + if (!mUidStateChangedCallbacks.containsKey(callback)) { + throw new IllegalStateException("Callback is not registered."); + } + mUidStateChangedCallbacks.remove(callback); + } + + @Override + public void updateAppWidgetVisibility(SparseArray<String> uidPackageNames, boolean visible) { + int numUids = uidPackageNames.size(); + for (int i = 0; i < numUids; i++) { + int uid = uidPackageNames.keyAt(i); + mPendingVisibleAppWidget.put(uid, visible); + + commitUidPendingState(uid); + } + } + + @Override + public void updateUidProcState(int uid, int procState, int capability) { + if (procState == PROCESS_STATE_NONEXISTENT) { + mUidStates.delete(uid); + mPendingUidStates.delete(uid); + mCapability.delete(uid); + mPendingCapability.delete(uid); + mVisibleAppWidget.delete(uid); + mPendingVisibleAppWidget.delete(uid); + mPendingCommitTime.delete(uid); + return; + } + + int uidState = processStateToUidState(procState); + + int prevUidState = mUidStates.get(uid, AppOpsManager.MIN_PRIORITY_UID_STATE); + int prevCapability = mCapability.get(uid, PROCESS_CAPABILITY_NONE); + long pendingStateCommitTime = mPendingCommitTime.get(uid, 0); + if (uidState != prevUidState || capability != prevCapability) { + mPendingUidStates.put(uid, uidState); + mPendingCapability.put(uid, capability); + + if (uidState < prevUidState + || (uidState <= UID_STATE_MAX_LAST_NON_RESTRICTED + && prevUidState > UID_STATE_MAX_LAST_NON_RESTRICTED)) { + // We are moving to a more important state, or the new state may be in the + // foreground and the old state is in the background, then always do it + // immediately. + commitUidPendingState(uid); + } else if (uidState == prevUidState && capability != prevCapability) { + // No change on process state, but process capability has changed. + commitUidPendingState(uid); + } else if (uidState <= UID_STATE_MAX_LAST_NON_RESTRICTED) { + // We are moving to a less important state, but it doesn't cross the restriction + // threshold. + commitUidPendingState(uid); + } else if (pendingStateCommitTime == 0) { + // We are moving to a less important state for the first time, + // delay the application for a bit. + final long settleTime; + if (prevUidState <= UID_STATE_TOP) { + settleTime = mConstants.TOP_STATE_SETTLE_TIME; + } else if (prevUidState <= UID_STATE_FOREGROUND_SERVICE) { + settleTime = mConstants.FG_SERVICE_STATE_SETTLE_TIME; + } else { + settleTime = mConstants.BG_STATE_SETTLE_TIME; + } + final long commitTime = mClock.elapsedRealtime() + settleTime; + mPendingCommitTime.put(uid, commitTime); + + mHandler.sendMessageDelayed(PooledLambda.obtainMessage( + AppOpsUidStateTrackerImpl::updateUidPendingStateIfNeeded, this, + uid), settleTime + 1); + } + } + } + + @Override + public void dumpUidState(PrintWriter pw, int uid, long nowElapsed) { + int state = mUidStates.get(uid, UID_STATE_CACHED); + // if no pendingState set to state to suppress output + int pendingState = mPendingUidStates.get(uid, state); + pw.print(" state="); + pw.println(AppOpsManager.getUidStateName(state)); + if (state != pendingState) { + pw.print(" pendingState="); + pw.println(AppOpsManager.getUidStateName(pendingState)); + } + int capability = mCapability.get(uid, PROCESS_CAPABILITY_NONE); + // if no pendingCapability set to capability to suppress output + int pendingCapability = mPendingCapability.get(uid, capability); + pw.print(" capability="); + ActivityManager.printCapabilitiesFull(pw, capability); + pw.println(); + if (capability != pendingCapability) { + pw.print(" pendingCapability="); + ActivityManager.printCapabilitiesFull(pw, pendingCapability); + pw.println(); + } + boolean appWidgetVisible = mVisibleAppWidget.get(uid, false); + // if no pendingAppWidgetVisible set to appWidgetVisible to suppress output + boolean pendingAppWidgetVisible = mPendingVisibleAppWidget.get(uid, appWidgetVisible); + pw.print(" appWidgetVisible="); + pw.println(appWidgetVisible); + if (appWidgetVisible != pendingAppWidgetVisible) { + pw.print(" pendingAppWidgetVisible="); + pw.println(pendingAppWidgetVisible); + } + long pendingStateCommitTime = mPendingCommitTime.get(uid, 0); + if (pendingStateCommitTime != 0) { + pw.print(" pendingStateCommitTime="); + TimeUtils.formatDuration(pendingStateCommitTime, nowElapsed, pw); + pw.println(); + } + } + + private void updateUidPendingStateIfNeeded(int uid) { + updateUidPendingStateIfNeededLocked(uid); + } + + private void updateUidPendingStateIfNeededLocked(int uid) { + long pendingCommitTime = mPendingCommitTime.get(uid, 0); + if (pendingCommitTime != 0) { + long currentTime = mClock.elapsedRealtime(); + if (currentTime < mPendingCommitTime.get(uid)) { + return; + } + commitUidPendingState(uid); + } + } + + private void commitUidPendingState(int uid) { + int pendingUidState = mPendingUidStates.get(uid, UID_STATE_CACHED); + int pendingCapability = mPendingCapability.get(uid, PROCESS_CAPABILITY_NONE); + boolean pendingVisibleAppWidget = mPendingVisibleAppWidget.get(uid, false); + + int uidState = mUidStates.get(uid, UID_STATE_CACHED); + int capability = mCapability.get(uid, PROCESS_CAPABILITY_NONE); + boolean visibleAppWidget = mVisibleAppWidget.get(uid, false); + + if (uidState != pendingUidState + || capability != pendingCapability + || visibleAppWidget != pendingVisibleAppWidget) { + boolean foregroundChange = uidState <= UID_STATE_MAX_LAST_NON_RESTRICTED + != pendingUidState > UID_STATE_MAX_LAST_NON_RESTRICTED + || capability != pendingCapability + || visibleAppWidget != pendingVisibleAppWidget; + + for (int i = 0; i < mUidStateChangedCallbacks.size(); i++) { + UidStateChangedCallback cb = mUidStateChangedCallbacks.keyAt(i); + Handler h = mUidStateChangedCallbacks.valueAt(i); + + h.sendMessage(PooledLambda.obtainMessage(UidStateChangedCallback::onUidStateChanged, + cb, uid, uidState, foregroundChange)); + } + } + + mUidStates.put(uid, pendingUidState); + mCapability.put(uid, pendingCapability); + mVisibleAppWidget.put(uid, pendingVisibleAppWidget); + + mPendingUidStates.delete(uid); + mPendingCapability.delete(uid); + mPendingVisibleAppWidget.delete(uid); + mPendingCommitTime.delete(uid); + } + + private @ProcessCapability int getUidCapability(int uid) { + return mCapability.get(uid, ActivityManager.PROCESS_CAPABILITY_NONE); + } + + private boolean getUidVisibleAppWidget(int uid) { + return mVisibleAppWidget.get(uid, false); + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java index 82334f29099d..56c373da87f7 100644 --- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java @@ -15,10 +15,8 @@ */ package com.android.server.appop; -import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.MODE_ERRORED; -import static android.app.AppOpsManager.MODE_FOREGROUND; import static android.app.AppOpsManager.OP_COARSE_LOCATION; import static android.app.AppOpsManager.OP_FLAGS_ALL; import static android.app.AppOpsManager.OP_READ_SMS; @@ -41,7 +39,6 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.nullable; -import android.app.ActivityManager; import android.app.AppOpsManager.OpEntry; import android.app.AppOpsManager.PackageOps; import android.content.ContentResolver; @@ -335,124 +332,6 @@ public class AppOpsServiceTest { assertThat(getLoggedOps()).isNull(); } - private void setupProcStateTests() { - // For the location proc state tests - mAppOpsService.setMode(OP_COARSE_LOCATION, mMyUid, sMyPackageName, MODE_FOREGROUND); - mAppOpsService.mConstants.FG_SERVICE_STATE_SETTLE_TIME = 0; - mAppOpsService.mConstants.TOP_STATE_SETTLE_TIME = 0; - mAppOpsService.mConstants.BG_STATE_SETTLE_TIME = 0; - } - - @Test - public void testUidProcStateChange_cachedToTopToCached() throws Exception { - setupProcStateTests(); - - mAppOpsService.updateUidProcState(mMyUid, ActivityManager.PROCESS_STATE_CACHED_EMPTY, - ActivityManager.PROCESS_CAPABILITY_NONE); - assertThat(mAppOpsService.noteOperation(OP_COARSE_LOCATION, mMyUid, sMyPackageName, null, - false, null, false).getOpMode()).isNotEqualTo(MODE_ALLOWED); - - mAppOpsService.updateUidProcState(mMyUid, ActivityManager.PROCESS_STATE_TOP, - ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION); - assertThat(mAppOpsService.noteOperation(OP_COARSE_LOCATION, mMyUid, sMyPackageName, null, - false, null, false).getOpMode()).isEqualTo(MODE_ALLOWED); - - mAppOpsService.updateUidProcState(mMyUid, ActivityManager.PROCESS_STATE_CACHED_EMPTY, - ActivityManager.PROCESS_CAPABILITY_NONE); - // Second time to make sure that settle time is overcome - Thread.sleep(50); - mAppOpsService.updateUidProcState(mMyUid, ActivityManager.PROCESS_STATE_CACHED_EMPTY, - ActivityManager.PROCESS_CAPABILITY_NONE); - assertThat(mAppOpsService.noteOperation(OP_COARSE_LOCATION, mMyUid, sMyPackageName, null, - false, null, false).getOpMode()).isNotEqualTo(MODE_ALLOWED); - } - - @Test - public void testUidProcStateChange_cachedToFgs() { - setupProcStateTests(); - mAppOpsService.updateUidProcState(mMyUid, ActivityManager.PROCESS_STATE_CACHED_EMPTY, - ActivityManager.PROCESS_CAPABILITY_NONE); - assertThat(mAppOpsService.noteOperation(OP_COARSE_LOCATION, mMyUid, sMyPackageName, null, - false, null, false).getOpMode()).isNotEqualTo(MODE_ALLOWED); - - mAppOpsService.updateUidProcState(mMyUid, PROCESS_STATE_FOREGROUND_SERVICE, - ActivityManager.PROCESS_CAPABILITY_NONE); - assertThat(mAppOpsService.noteOperation(OP_COARSE_LOCATION, mMyUid, sMyPackageName, null, - false, null, false).getOpMode()).isNotEqualTo(MODE_ALLOWED); - } - - @Test - public void testUidProcStateChange_cachedToFgsLocation() { - setupProcStateTests(); - - mAppOpsService.updateUidProcState(mMyUid, ActivityManager.PROCESS_STATE_CACHED_EMPTY, - ActivityManager.PROCESS_CAPABILITY_NONE); - assertThat(mAppOpsService.noteOperation(OP_COARSE_LOCATION, mMyUid, sMyPackageName, null, - false, null, false).getOpMode()).isNotEqualTo(MODE_ALLOWED); - - mAppOpsService.updateUidProcState(mMyUid, PROCESS_STATE_FOREGROUND_SERVICE, - ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION); - assertThat(mAppOpsService.noteOperation(OP_COARSE_LOCATION, mMyUid, sMyPackageName, null, - false, null, false).getOpMode()).isEqualTo(MODE_ALLOWED); - } - - @Test - public void testUidProcStateChange_topToFgs() throws Exception { - setupProcStateTests(); - - mAppOpsService.updateUidProcState(mMyUid, ActivityManager.PROCESS_STATE_CACHED_EMPTY, - ActivityManager.PROCESS_CAPABILITY_NONE); - assertThat(mAppOpsService.noteOperation(OP_COARSE_LOCATION, mMyUid, sMyPackageName, null, - false, null, false).getOpMode()).isNotEqualTo(MODE_ALLOWED); - - mAppOpsService.updateUidProcState(mMyUid, ActivityManager.PROCESS_STATE_TOP, - ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION); - assertThat(mAppOpsService.noteOperation(OP_COARSE_LOCATION, mMyUid, sMyPackageName, null, - false, null, false).getOpMode()).isEqualTo(MODE_ALLOWED); - - mAppOpsService.updateUidProcState(mMyUid, PROCESS_STATE_FOREGROUND_SERVICE, - ActivityManager.PROCESS_CAPABILITY_NONE); - // Second time to make sure that settle time is overcome - Thread.sleep(50); - mAppOpsService.updateUidProcState(mMyUid, PROCESS_STATE_FOREGROUND_SERVICE, - ActivityManager.PROCESS_CAPABILITY_NONE); - assertThat(mAppOpsService.noteOperation(OP_COARSE_LOCATION, mMyUid, sMyPackageName, null, - false, null, false).getOpMode()).isNotEqualTo(MODE_ALLOWED); - } - - @Test - public void testUidProcStateChange_topToFgsLocationToFgs() throws Exception { - setupProcStateTests(); - - mAppOpsService.updateUidProcState(mMyUid, ActivityManager.PROCESS_STATE_CACHED_EMPTY, - ActivityManager.PROCESS_CAPABILITY_NONE); - assertThat(mAppOpsService.noteOperation(OP_COARSE_LOCATION, mMyUid, sMyPackageName, null, - false, null, false).getOpMode()).isNotEqualTo(MODE_ALLOWED); - - mAppOpsService.updateUidProcState(mMyUid, ActivityManager.PROCESS_STATE_TOP, - ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION); - assertThat(mAppOpsService.noteOperation(OP_COARSE_LOCATION, mMyUid, sMyPackageName, null, - false, null, false).getOpMode()).isEqualTo(MODE_ALLOWED); - - mAppOpsService.updateUidProcState(mMyUid, PROCESS_STATE_FOREGROUND_SERVICE, - ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION); - // Second time to make sure that settle time is overcome - Thread.sleep(50); - mAppOpsService.updateUidProcState(mMyUid, PROCESS_STATE_FOREGROUND_SERVICE, - ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION); - assertThat(mAppOpsService.noteOperation(OP_COARSE_LOCATION, mMyUid, sMyPackageName, null, - false, null, false).getOpMode()).isEqualTo(MODE_ALLOWED); - - mAppOpsService.updateUidProcState(mMyUid, PROCESS_STATE_FOREGROUND_SERVICE, - ActivityManager.PROCESS_CAPABILITY_NONE); - // Second time to make sure that settle time is overcome - Thread.sleep(50); - mAppOpsService.updateUidProcState(mMyUid, PROCESS_STATE_FOREGROUND_SERVICE, - ActivityManager.PROCESS_CAPABILITY_NONE); - assertThat(mAppOpsService.noteOperation(OP_COARSE_LOCATION, mMyUid, sMyPackageName, null, - false, null, false).getOpMode()).isNotEqualTo(MODE_ALLOWED); - } - private List<PackageOps> getLoggedOps() { return mAppOpsService.getOpsForPackage(mMyUid, sMyPackageName, null /* all ops */); } diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java new file mode 100644 index 000000000000..3bf2db206217 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java @@ -0,0 +1,548 @@ +/* + * 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.MODE_FOREGROUND; +import static android.app.AppOpsManager.MODE_IGNORED; +import static android.app.AppOpsManager.OP_CAMERA; +import static android.app.AppOpsManager.OP_COARSE_LOCATION; +import static android.app.AppOpsManager.OP_FINE_LOCATION; +import static android.app.AppOpsManager.OP_RECORD_AUDIO; +import static android.app.AppOpsManager.OP_WIFI_SCAN; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.verify; + +import android.app.ActivityManager; +import android.app.ActivityManagerInternal; +import android.app.AppOpsManager; +import android.os.Handler; +import android.os.Message; +import android.util.SparseArray; + +import com.android.dx.mockito.inline.extended.ExtendedMockito; +import com.android.dx.mockito.inline.extended.StaticMockitoSession; +import com.android.internal.os.Clock; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.quality.Strictness; + +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; + +public class AppOpsUidStateTrackerTest { + + private static final int UID = 10001; + + // An op code that's not location/cam/mic so that we can test the code for evaluating mode + // without a specific capability associated with it. + public static final int OP_NO_CAPABILITIES = OP_WIFI_SCAN; + + @Mock + ActivityManagerInternal mAmi; + + @Mock + Handler mHandler; + + @Mock + AppOpsService.Constants mConstants; + + AppOpsUidStateTrackerTestClock mClock = new AppOpsUidStateTrackerTestClock(); + + AppOpsUidStateTracker mIntf; + + StaticMockitoSession mSession; + + @Before + public void setUp() { + mSession = ExtendedMockito.mockitoSession() + .initMocks(this) + .strictness(Strictness.LENIENT) + .startMocking(); + mConstants.TOP_STATE_SETTLE_TIME = 10 * 1000L; + mConstants.FG_SERVICE_STATE_SETTLE_TIME = 5 * 1000L; + mConstants.BG_STATE_SETTLE_TIME = 1 * 1000L; + mIntf = new AppOpsUidStateTrackerImpl(mAmi, mHandler, mClock, mConstants); + } + + @After + public void tearDown() { + mSession.finishMocking(); + } + + /** + * This class makes the assumption that all ops are restricted at the same state, this is likely + * to be the case forever or become obsolete with the capability mechanism. If this fails + * something in {@link AppOpsUidStateTrackerImpl} might break when reporting if foreground mode + * might change. + */ + @Test + public void testConstantFirstUnrestrictedUidState() { + for (int i = 0; i < AppOpsManager.getNumOps(); i++) { + assertEquals(AppOpsManager.UID_STATE_MAX_LAST_NON_RESTRICTED, + AppOpsManager.resolveFirstUnrestrictedUidState(i)); + } + } + + @Test + public void testNoCapability() { + procStateBuilder(UID) + .foregroundState() + .update(); + + assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND)); + assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND)); + assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND)); + assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND)); + } + + @Test + public void testForegroundWithMicrophoneCapability() { + procStateBuilder(UID) + .foregroundState() + .microphoneCapability() + .update(); + + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND)); + + assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND)); + assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND)); + assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND)); + } + + @Test + public void testBackgroundWithMicrophoneCapability() { + procStateBuilder(UID) + .backgroundState() + .microphoneCapability() + .update(); + + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND)); + + assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND)); + assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND)); + assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND)); + } + + @Test + public void testForegroundWithCameraCapability() { + procStateBuilder(UID) + .foregroundState() + .cameraCapability() + .update(); + + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND)); + + assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND)); + assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND)); + assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND)); + } + + @Test + public void testBackgroundWithCameraCapability() { + procStateBuilder(UID) + .backgroundState() + .cameraCapability() + .update(); + + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND)); + + assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND)); + assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND)); + assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND)); + } + + @Test + public void testForegroundWithLocationCapability() { + procStateBuilder(UID) + .foregroundState() + .locationCapability() + .update(); + + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND)); + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND)); + + assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND)); + assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND)); + } + + @Test + public void testBackgroundWithLocationCapability() { + procStateBuilder(UID) + .backgroundState() + .locationCapability() + .update(); + + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND)); + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND)); + + assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND)); + assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND)); + } + + @Test + public void testForegroundNotCapabilitiesTracked() { + procStateBuilder(UID) + .foregroundState() + .update(); + + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_NO_CAPABILITIES, MODE_FOREGROUND)); + } + + @Test + public void testBackgroundNotCapabilitiesTracked() { + procStateBuilder(UID) + .backgroundState() + .update(); + + assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_NO_CAPABILITIES, MODE_FOREGROUND)); + } + + @Test + public void testBackgroundToForegroundTransition() { + procStateBuilder(UID) + .backgroundState() + .update(); + assertBackground(UID); + + procStateBuilder(UID) + .foregroundState() + .update(); + assertForeground(UID); + } + + @Test + public void testForegroundToBackgroundTransition() { + procStateBuilder(UID) + .foregroundState() + .update(); + assertForeground(UID); + + procStateBuilder(UID) + .backgroundState() + .update(); + // Still in foreground due to settle time + assertForeground(UID); + + AtomicReference<Message> messageAtomicReference = new AtomicReference<>(); + AtomicLong delayAtomicReference = new AtomicLong(); + + getPostDelayedMessageArguments(messageAtomicReference, delayAtomicReference); + Message message = messageAtomicReference.get(); + long delay = delayAtomicReference.get(); + + assertNotNull(message); + assertEquals(mConstants.TOP_STATE_SETTLE_TIME + 1, delay); + + mClock.advanceTime(mConstants.TOP_STATE_SETTLE_TIME + 1); + message.getCallback().run(); + assertBackground(UID); + } + + @Test + public void testForegroundServiceToBackgroundTransition() { + procStateBuilder(UID) + .foregroundServiceState() + .update(); + assertForeground(UID); + + procStateBuilder(UID) + .backgroundState() + .update(); + // Still in foreground due to settle time + assertForeground(UID); + + AtomicReference<Message> messageAtomicReference = new AtomicReference<>(); + AtomicLong delayAtomicReference = new AtomicLong(); + + getPostDelayedMessageArguments(messageAtomicReference, delayAtomicReference); + Message message = messageAtomicReference.get(); + long delay = delayAtomicReference.get(); + + assertNotNull(message); + assertEquals(mConstants.FG_SERVICE_STATE_SETTLE_TIME + 1, delay); + + mClock.advanceTime(mConstants.FG_SERVICE_STATE_SETTLE_TIME + 1); + message.getCallback().run(); + assertBackground(UID); + } + + @Test + public void testEarlyUpdateDoesntCommit() { + procStateBuilder(UID) + .foregroundServiceState() + .update(); + assertForeground(UID); + + procStateBuilder(UID) + .backgroundState() + .update(); + // Still in foreground due to settle time + assertForeground(UID); + + AtomicReference<Message> messageAtomicReference = new AtomicReference<>(); + + getPostDelayedMessageArguments(messageAtomicReference, null); + Message message = messageAtomicReference.get(); + + // 1 ms short of settle time + mClock.advanceTime(mConstants.FG_SERVICE_STATE_SETTLE_TIME - 1); + message.getCallback().run(); + assertForeground(UID); + } + + @Test + public void testMicrophoneCapabilityAdded() { + procStateBuilder(UID) + .backgroundState() + .update(); + + procStateBuilder(UID) + .backgroundState() + .microphoneCapability() + .update(); + + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND)); + } + + @Test + public void testMicrophoneCapabilityRemoved() { + procStateBuilder(UID) + .backgroundState() + .microphoneCapability() + .update(); + + procStateBuilder(UID) + .backgroundState() + .update(); + + assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND)); + } + + @Test + public void testCameraCapabilityAdded() { + procStateBuilder(UID) + .backgroundState() + .update(); + + procStateBuilder(UID) + .backgroundState() + .cameraCapability() + .update(); + + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND)); + } + + @Test + public void testCameraCapabilityRemoved() { + procStateBuilder(UID) + .backgroundState() + .cameraCapability() + .update(); + + procStateBuilder(UID) + .backgroundState() + .update(); + + assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND)); + } + + @Test + public void testLocationCapabilityAdded() { + procStateBuilder(UID) + .backgroundState() + .update(); + + procStateBuilder(UID) + .backgroundState() + .locationCapability() + .update(); + + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND)); + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND)); + } + + @Test + public void testLocationCapabilityRemoved() { + procStateBuilder(UID) + .backgroundState() + .locationCapability() + .update(); + + procStateBuilder(UID) + .backgroundState() + .update(); + + assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND)); + assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND)); + } + + @Test + public void testVisibleAppWidget() { + procStateBuilder(UID) + .backgroundState() + .update(); + + SparseArray<String> appPackageNames = new SparseArray<>(); + appPackageNames.put(UID, ""); + mIntf.updateAppWidgetVisibility(appPackageNames, true); + + assertForeground(UID); + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND)); + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND)); + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND)); + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND)); + } + + @Test + public void testPendingTop() { + procStateBuilder(UID) + .backgroundState() + .update(); + + doReturn(true).when(mAmi).isPendingTopUid(eq(UID)); + + assertForeground(UID); + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND)); + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND)); + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND)); + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND)); + } + + @Test + public void testTempAllowlist() { + procStateBuilder(UID) + .backgroundState() + .update(); + + doReturn(true).when(mAmi).isTempAllowlistedForFgsWhileInUse(eq(UID)); + + assertForeground(UID); + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND)); + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND)); + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND)); + assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND)); + } + + /* If testForegroundNotCapabilitiesTracked fails, this assertion is probably incorrect */ + private void assertForeground(int uid) { + assertEquals(MODE_ALLOWED, mIntf.evalMode(uid, OP_NO_CAPABILITIES, MODE_FOREGROUND)); + } + + /* If testBackgroundNotCapabilitiesTracked fails, this assertion is probably incorrect */ + private void assertBackground(int uid) { + assertEquals(MODE_IGNORED, mIntf.evalMode(uid, OP_NO_CAPABILITIES, MODE_FOREGROUND)); + } + + private void getPostDelayedMessageArguments(AtomicReference<Message> message, + AtomicLong delay) { + + ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class); + ArgumentCaptor<Long> delayCaptor = ArgumentCaptor.forClass(Long.class); + + verify(mHandler).sendMessageDelayed(messageCaptor.capture(), delayCaptor.capture()); + + if (message != null) { + message.set(messageCaptor.getValue()); + } + if (delay != null) { + delay.set(delayCaptor.getValue()); + } + } + + private UidProcStateUpdateBuilder procStateBuilder(int uid) { + return new UidProcStateUpdateBuilder(mIntf, uid); + } + + private static class UidProcStateUpdateBuilder { + private AppOpsUidStateTracker mIntf; + private int mUid; + private int mProcState = ActivityManager.PROCESS_STATE_NONEXISTENT; + private int mCapability = 0; + + private UidProcStateUpdateBuilder(AppOpsUidStateTracker intf, int uid) { + mUid = uid; + mIntf = intf; + } + + public void update() { + mIntf.updateUidProcState(mUid, mProcState, mCapability); + } + + public UidProcStateUpdateBuilder persistentState() { + mProcState = ActivityManager.PROCESS_STATE_PERSISTENT; + return this; + } + + public UidProcStateUpdateBuilder foregroundState() { + mProcState = ActivityManager.PROCESS_STATE_TOP; + return this; + } + + public UidProcStateUpdateBuilder foregroundServiceState() { + mProcState = ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; + return this; + } + + public UidProcStateUpdateBuilder backgroundState() { + mProcState = ActivityManager.PROCESS_STATE_SERVICE; + return this; + } + + public UidProcStateUpdateBuilder cachedState() { + mProcState = ActivityManager.PROCESS_STATE_CACHED_ACTIVITY; + return this; + } + + public UidProcStateUpdateBuilder locationCapability() { + mCapability |= ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION; + return this; + } + + public UidProcStateUpdateBuilder cameraCapability() { + mCapability |= ActivityManager.PROCESS_CAPABILITY_FOREGROUND_CAMERA; + return this; + } + + public UidProcStateUpdateBuilder microphoneCapability() { + mCapability |= ActivityManager.PROCESS_CAPABILITY_FOREGROUND_MICROPHONE; + return this; + } + } + + private static class AppOpsUidStateTrackerTestClock extends Clock { + + long mElapsedRealTime = 0x5f3759df; + + @Override + public long elapsedRealtime() { + return mElapsedRealTime; + } + + void advanceTime(long time) { + mElapsedRealTime += time; + } + } +} |