Extract uid state foreground logic from AppOpsService

Bug: 241447638
Test: CtsAppOpsTestCases, CtsAppOpsTestCases2
Change-Id: I73672b592f6b8548fb33692f63aca8dcb83a85c9
This commit is contained in:
Evan Severson
2022-09-08 17:26:31 -07:00
parent af8f2d2779
commit b574e1c62f
6 changed files with 1144 additions and 400 deletions

View File

@@ -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;
}
/**

View File

@@ -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:");

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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 */);
}

View File

@@ -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;
}
}
}