Extract uid state foreground logic from AppOpsService
Bug: 241447638 Test: CtsAppOpsTestCases, CtsAppOpsTestCases2 Change-Id: I73672b592f6b8548fb33692f63aca8dcb83a85c9
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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,58 @@ 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);
|
||||
|
||||
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;
|
||||
if (uidState != null && foregroundModeMayChange && uidState.hasForegroundWatchers) {
|
||||
for (int fgi = uidState.foregroundOps.size() - 1; fgi >= 0; fgi--) {
|
||||
if (!uidState.foregroundOps.valueAt(fgi)) {
|
||||
continue;
|
||||
}
|
||||
final long commitTime = SystemClock.elapsedRealtime() + settleTime;
|
||||
uidState.pendingStateCommitTime = commitTime;
|
||||
final int code = uidState.foregroundOps.keyAt(fgi);
|
||||
|
||||
mHandler.sendMessageDelayed(
|
||||
PooledLambda.obtainMessage(AppOpsService::updatePendingState, this,
|
||||
commitTime + 1, uid), settleTime + 1);
|
||||
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 (uidState.pkgOps != null) {
|
||||
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);
|
||||
@@ -2163,7 +2097,50 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch
|
||||
AttributedOp attributedOp = op.mAttributions.valueAt(
|
||||
attributionNum);
|
||||
|
||||
attributedOp.onUidStateChanged(newState);
|
||||
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) {
|
||||
getUidStateTracker().updateUidProcState(uid, procState, capability);
|
||||
if (!mUidStates.contains(uid)) {
|
||||
UidState uidState = new UidState(uid);
|
||||
mUidStates.put(uid, uidState);
|
||||
onUidStateChanged(uid,
|
||||
AppOpsUidStateTracker.processStateToUidState(procState), false);
|
||||
}
|
||||
|
||||
if (!USE_DELAYED_UID_STATE_CHANGES_FOR_ATTR_OP_UPDATES) {
|
||||
UidState uidState = mUidStates.get(uid);
|
||||
|
||||
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(
|
||||
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;
|
||||
}
|
||||
|
||||
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:");
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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 */);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user