summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/AppOpsManager.java2
-rw-r--r--services/core/java/com/android/server/appop/AppOpsService.java417
-rw-r--r--services/core/java/com/android/server/appop/AppOpsUidStateTracker.java118
-rw-r--r--services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java332
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java121
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java548
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;
+ }
+ }
+}