Work on issue #78480444: Start tracking uid state in app ops
Introduce new app op mode that uses uid state to determine whether
the caller has access. This will determine what noteOp() and
startOp() return, based on the state of the uid.
Bug: 78480444
Test: atest FrameworksServicesTests:AppOpsServiceTest
Test: atest CtsPermissionTestCases:AppOpsTest
Change-Id: I12b744b74f3129782dbda9567043f5170919b5d3
Merged-In: I55fd74023cc4dae8151372e28c3afc7d259c7a1c
diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java
index d818bd6..13503e6 100644
--- a/services/core/java/com/android/server/AppOpsService.java
+++ b/services/core/java/com/android/server/AppOpsService.java
@@ -48,6 +48,7 @@
import android.util.AtomicFile;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import android.util.TimeUtils;
import android.util.Xml;
@@ -108,6 +109,9 @@
// Write at most every 30 minutes.
static final long WRITE_DELAY = DEBUG ? 1000 : 30*60*1000;
+ // How long we want for a drop in uid state to settle before applying it.
+ static final long STATE_SETTLE_TIME = 10*1000;
+
// Constant meaning that any UID should be matched when dispatching callbacks
private static final int UID_ANY = -2;
@@ -162,13 +166,6 @@
"rc", // UID_STATE_CACHED
};
- static final String[] MODE_NAMES = new String[] {
- "allow", // MODE_ALLOWED
- "ignore", // MODE_IGNORED
- "deny", // MODE_ERRORED
- "default", // MODE_DEFAULT
- };
-
Context mContext;
final AtomicFile mFile;
final Handler mHandler;
@@ -194,6 +191,8 @@
@VisibleForTesting
final SparseArray<UidState> mUidStates = new SparseArray<>();
+ long mLastUptime;
+
/*
* These are app op restrictions imposed per user from various parties.
*/
@@ -202,11 +201,17 @@
@VisibleForTesting
static final class UidState {
public final int uid;
+
public int state = UID_STATE_CACHED;
+ public int pendingState = UID_STATE_CACHED;
+ public long pendingStateCommitTime;
+
public int startNesting;
public ArrayMap<String, Ops> pkgOps;
public SparseIntArray opModes;
+ public SparseBooleanArray foregroundOps;
+
public UidState(int uid) {
this.uid = uid;
}
@@ -220,6 +225,32 @@
return (pkgOps == null || pkgOps.isEmpty())
&& (opModes == null || opModes.size() <= 0);
}
+
+ int evalMode(int mode) {
+ if (mode == AppOpsManager.MODE_FOREGROUND) {
+ return state <= UID_STATE_FOREGROUND_SERVICE
+ ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED;
+ }
+ return mode;
+ }
+
+ public void evalForegroundOps() {
+ SparseBooleanArray which = null;
+ if (pkgOps != null) {
+ for (int i = pkgOps.size() - 1; i >= 0; i--) {
+ Ops ops = pkgOps.valueAt(i);
+ for (int j = ops.size() - 1; j >= 0; j--) {
+ if (ops.valueAt(j).mode == AppOpsManager.MODE_FOREGROUND) {
+ if (which == null) {
+ which = new SparseBooleanArray();
+ }
+ which.put(ops.keyAt(j), true);
+ }
+ }
+ }
+ }
+ foregroundOps = which;
+ }
}
final static class Ops extends SparseArray<Op> {
@@ -267,6 +298,10 @@
}
return false;
}
+
+ int getMode() {
+ return uidState.evalMode(mode);
+ }
}
final SparseArray<ArraySet<ModeCallback>> mOpModeWatchers = new SparseArray<>();
@@ -575,7 +610,16 @@
synchronized (this) {
final UidState uidState = getUidStateLocked(uid, true);
final int newState = PROCESS_STATE_TO_UID_STATE[procState];
- if (uidState != null && uidState.state != newState) {
+ if (uidState != null && uidState.pendingState != newState) {
+ if (newState < uidState.state) {
+ // We are moving to a more important state, always do it immediately.
+ uidState.state = newState;
+ uidState.pendingStateCommitTime = 0;
+ } else if (uidState.pendingStateCommitTime == 0) {
+ // We are moving to a less important state for the first time,
+ // delay the application for a bit.
+ uidState.pendingStateCommitTime = SystemClock.uptimeMillis() + STATE_SETTLE_TIME;
+ }
if (uidState.startNesting != 0) {
// There is some actively running operation... need to find it
// and appropriately update its state.
@@ -585,13 +629,13 @@
for (int j = ops.size() - 1; j >= 0; j--) {
final Op op = ops.valueAt(j);
if (op.startNesting > 0) {
- op.time[uidState.state] = now;
+ op.time[uidState.pendingState] = now;
op.time[newState] = now;
}
}
}
}
- uidState.state = newState;
+ uidState.pendingState = newState;
}
}
}
@@ -887,6 +931,9 @@
if (op != null) {
if (op.mode != mode) {
op.mode = mode;
+ if (uidState != null) {
+ uidState.evalForegroundOps();
+ }
ArraySet<ModeCallback> cbs = mOpModeWatchers.get(code);
if (cbs != null) {
if (repCbs == null) {
@@ -1046,6 +1093,7 @@
Map<String, Ops> packages = uidState.pkgOps;
Iterator<Map.Entry<String, Ops>> it = packages.entrySet().iterator();
+ boolean uidChanged = false;
while (it.hasNext()) {
Map.Entry<String, Ops> ent = it.next();
String packageName = ent.getKey();
@@ -1060,6 +1108,7 @@
&& curOp.mode != AppOpsManager.opToDefaultMode(curOp.op)) {
curOp.mode = AppOpsManager.opToDefaultMode(curOp.op);
changed = true;
+ uidChanged = true;
callbacks = addCallbacks(callbacks, curOp.op, curOp.uid, packageName,
mOpModeWatchers.get(curOp.op));
callbacks = addCallbacks(callbacks, curOp.op, curOp.uid, packageName,
@@ -1076,6 +1125,9 @@
if (uidState.isDefault()) {
mUidStates.remove(uidState.uid);
}
+ if (uidChanged) {
+ uidState.evalForegroundOps();
+ }
}
if (changed) {
@@ -1197,7 +1249,7 @@
if (op == null) {
return AppOpsManager.opToDefaultMode(code);
}
- return op.mode;
+ return op.mode == AppOpsManager.MODE_FOREGROUND ? AppOpsManager.MODE_ALLOWED : op.mode;
}
}
@@ -1352,9 +1404,9 @@
// If there is a non-default per UID policy (we set UID op mode only if
// non-default) it takes over, otherwise use the per package policy.
if (uidState.opModes != null && uidState.opModes.indexOfKey(switchCode) >= 0) {
- final int uidMode = uidState.opModes.get(switchCode);
+ final int uidMode = uidState.evalMode(uidState.opModes.get(switchCode));
if (uidMode != AppOpsManager.MODE_ALLOWED) {
- if (DEBUG) Slog.d(TAG, "noteOperation: reject #" + op.mode + " for code "
+ if (DEBUG) Slog.d(TAG, "noteOperation: uid reject #" + uidMode + " for code "
+ switchCode + " (" + code + ") uid " + uid + " package "
+ packageName);
op.rejectTime[uidState.state] = System.currentTimeMillis();
@@ -1362,12 +1414,13 @@
}
} else {
final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, true) : op;
- if (switchOp.mode != AppOpsManager.MODE_ALLOWED) {
- if (DEBUG) Slog.d(TAG, "noteOperation: reject #" + op.mode + " for code "
+ final int mode = switchOp.getMode();
+ if (mode != AppOpsManager.MODE_ALLOWED) {
+ if (DEBUG) Slog.d(TAG, "noteOperation: reject #" + mode + " for code "
+ switchCode + " (" + code + ") uid " + uid + " package "
+ packageName);
op.rejectTime[uidState.state] = System.currentTimeMillis();
- return switchOp.mode;
+ return mode;
}
}
if (DEBUG) Slog.d(TAG, "noteOperation: allowing code " + code + " uid " + uid
@@ -1458,10 +1511,10 @@
// If there is a non-default per UID policy (we set UID op mode only if
// non-default) it takes over, otherwise use the per package policy.
if (uidState.opModes != null && uidState.opModes.indexOfKey(switchCode) >= 0) {
- final int uidMode = uidState.opModes.get(switchCode);
+ final int uidMode = uidState.evalMode(uidState.opModes.get(switchCode));
if (uidMode != AppOpsManager.MODE_ALLOWED
&& (!startIfModeDefault || uidMode != AppOpsManager.MODE_DEFAULT)) {
- if (DEBUG) Slog.d(TAG, "noteOperation: reject #" + op.mode + " for code "
+ if (DEBUG) Slog.d(TAG, "noteOperation: uid reject #" + uidMode + " for code "
+ switchCode + " (" + code + ") uid " + uid + " package "
+ resolvedPackageName);
op.rejectTime[uidState.state] = System.currentTimeMillis();
@@ -1469,13 +1522,14 @@
}
} else {
final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, true) : op;
- if (switchOp.mode != AppOpsManager.MODE_ALLOWED
- && (!startIfModeDefault || switchOp.mode != AppOpsManager.MODE_DEFAULT)) {
- if (DEBUG) Slog.d(TAG, "startOperation: reject #" + op.mode + " for code "
+ final int mode = switchOp.getMode();
+ if (mode != AppOpsManager.MODE_ALLOWED
+ && (!startIfModeDefault || mode != AppOpsManager.MODE_DEFAULT)) {
+ if (DEBUG) Slog.d(TAG, "startOperation: reject #" + mode + " for code "
+ switchCode + " (" + code + ") uid " + uid + " package "
+ resolvedPackageName);
op.rejectTime[uidState.state] = System.currentTimeMillis();
- return switchOp.mode;
+ return mode;
}
}
if (DEBUG) Slog.d(TAG, "startOperation: allowing code " + code + " uid " + uid
@@ -1642,6 +1696,19 @@
}
uidState = new UidState(uid);
mUidStates.put(uid, uidState);
+ } else {
+ if (uidState.pendingStateCommitTime != 0) {
+ if (uidState.pendingStateCommitTime < mLastUptime) {
+ uidState.state = uidState.pendingState;
+ uidState.pendingStateCommitTime = 0;
+ } else {
+ mLastUptime = SystemClock.uptimeMillis();
+ if (uidState.pendingStateCommitTime < mLastUptime) {
+ uidState.state = uidState.pendingState;
+ uidState.pendingStateCommitTime = 0;
+ }
+ }
+ }
}
return uidState;
}
@@ -1870,6 +1937,7 @@
if (uidState.pkgOps == null) {
continue;
}
+ boolean changed = false;
for (int j = 0; j < uidState.pkgOps.size(); j++) {
Ops ops = uidState.pkgOps.valueAt(j);
if (ops != null) {
@@ -1879,9 +1947,13 @@
AppOpsManager.OP_RUN_ANY_IN_BACKGROUND);
copy.mode = op.mode;
ops.put(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, copy);
+ changed = true;
}
}
}
+ if (changed) {
+ uidState.evalForegroundOps();
+ }
}
}
@@ -2077,6 +2149,10 @@
XmlUtils.skipCurrentTag(parser);
}
}
+ UidState uidState = getUidStateLocked(uid, false);
+ if (uidState != null) {
+ uidState.evalForegroundOps();
+ }
}
void writeState() {
@@ -2152,12 +2228,12 @@
out.attribute(null, "m", Integer.toString(op.getMode()));
}
for (int k = 0; k < _NUM_UID_STATE; k++) {
- final long time = op.getTimeFor(k);
+ final long time = op.getLastTimeFor(k);
if (time != 0) {
out.attribute(null, UID_STATE_TIME_ATTRS[k],
Long.toString(time));
}
- final long rejectTime = op.getRejectTimeFor(k);
+ final long rejectTime = op.getLastRejectTimeFor(k);
if (rejectTime != 0) {
out.attribute(null, UID_STATE_REJECT_ATTRS[k],
Long.toString(rejectTime));
@@ -2229,7 +2305,7 @@
dumpCommandHelp(pw);
}
- private int strOpToOp(String op, PrintWriter err) {
+ static private int strOpToOp(String op, PrintWriter err) {
try {
return AppOpsManager.strOpToOp(op);
} catch (IllegalArgumentException e) {
@@ -2247,8 +2323,8 @@
}
int strModeToMode(String modeStr, PrintWriter err) {
- for (int i = MODE_NAMES.length - 1; i >= 0; i--) {
- if (MODE_NAMES[i].equals(modeStr)) {
+ for (int i = AppOpsManager.MODE_NAMES.length - 1; i >= 0; i--) {
+ if (AppOpsManager.MODE_NAMES[i].equals(modeStr)) {
return i;
}
}
@@ -2470,7 +2546,7 @@
if (ops == null || ops.size() <= 0) {
pw.println("No operations.");
if (shell.op > AppOpsManager.OP_NONE && shell.op < AppOpsManager._NUM_OP) {
- pw.println("Default mode: " + AppOpsManager.modeToString(
+ pw.println("Default mode: " + AppOpsManager.modeToName(
AppOpsManager.opToDefaultMode(shell.op)));
}
return 0;
@@ -2482,7 +2558,7 @@
AppOpsManager.OpEntry ent = entries.get(j);
pw.print(AppOpsManager.opToName(ent.getOp()));
pw.print(": ");
- pw.print(AppOpsManager.modeToString(ent.getMode()));
+ pw.print(AppOpsManager.modeToName(ent.getMode()));
if (ent.getTime() != 0) {
pw.print("; time=");
TimeUtils.formatDuration(now - ent.getTime(), pw);
@@ -2636,7 +2712,10 @@
private void dumpHelp(PrintWriter pw) {
pw.println("AppOps service (appops) dump options:");
- pw.println(" none");
+ pw.println(" -h");
+ pw.println(" Print this help text.");
+ pw.println(" --op [OP]");
+ pw.println(" Limit output to data associated with the given app op code.");
}
private void dumpTimesLocked(PrintWriter pw, String firstPrefix, String prefix, long[] times,
@@ -2671,6 +2750,8 @@
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return;
+ int dumpOp = -1;
+
if (args != null) {
for (int i=0; i<args.length; i++) {
String arg = args[i];
@@ -2679,6 +2760,16 @@
return;
} else if ("-a".equals(arg)) {
// dump all data
+ } else if ("--op".equals(arg)) {
+ i++;
+ if (i >= args.length) {
+ pw.println("No argument for --op option");
+ return;
+ }
+ dumpOp = Shell.strOpToOp(args[i], pw);
+ if (dumpOp < 0) {
+ return;
+ }
} else if (arg.length() > 0 && arg.charAt(0) == '-'){
pw.println("Unknown option: " + arg);
return;
@@ -2693,13 +2784,21 @@
pw.println("Current AppOps Service state:");
final long now = System.currentTimeMillis();
final long nowElapsed = SystemClock.elapsedRealtime();
+ final long nowUptime = SystemClock.uptimeMillis();
final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
final Date date = new Date();
boolean needSep = false;
if (mOpModeWatchers.size() > 0) {
needSep = true;
- pw.println(" Op mode watchers:");
+ boolean printedHeader = false;
for (int i=0; i<mOpModeWatchers.size(); i++) {
+ if (dumpOp >= 0 && dumpOp != mOpModeWatchers.keyAt(i)) {
+ continue;
+ }
+ if (!printedHeader) {
+ pw.println(" Op mode watchers:");
+ printedHeader = true;
+ }
pw.print(" Op "); pw.print(AppOpsManager.opToName(mOpModeWatchers.keyAt(i)));
pw.println(":");
ArraySet<ModeCallback> callbacks = mOpModeWatchers.valueAt(i);
@@ -2722,7 +2821,7 @@
}
}
}
- if (mModeWatchers.size() > 0) {
+ if (mModeWatchers.size() > 0 && dumpOp < 0) {
needSep = true;
pw.println(" All op mode watchers:");
for (int i=0; i<mModeWatchers.size(); i++) {
@@ -2733,12 +2832,19 @@
}
if (mActiveWatchers.size() > 0) {
needSep = true;
- pw.println(" All op active watchers:");
+ boolean printedHeader = false;
for (int i = 0; i < mActiveWatchers.size(); i++) {
final SparseArray<ActiveCallback> activeWatchers = mActiveWatchers.valueAt(i);
if (activeWatchers.size() <= 0) {
continue;
}
+ if (dumpOp >= 0 && activeWatchers.indexOfKey(dumpOp) < 0) {
+ continue;
+ }
+ if (!printedHeader) {
+ pw.println(" All op active watchers:");
+ printedHeader = true;
+ }
pw.print(" ");
pw.print(Integer.toHexString(System.identityHashCode(
mActiveWatchers.keyAt(i))));
@@ -2746,6 +2852,9 @@
pw.print(" [");
final int opCount = activeWatchers.size();
for (i = 0; i < opCount; i++) {
+ if (i > 0) {
+ pw.print(' ');
+ }
pw.print(AppOpsManager.opToName(activeWatchers.keyAt(i)));
if (i < opCount - 1) {
pw.print(',');
@@ -2758,15 +2867,30 @@
}
if (mClients.size() > 0) {
needSep = true;
- pw.println(" Clients:");
+ boolean printedHeader = false;
for (int i=0; i<mClients.size(); i++) {
- pw.print(" "); pw.print(mClients.keyAt(i)); pw.println(":");
+ boolean printedClient = false;
ClientState cs = mClients.valueAt(i);
- pw.print(" "); pw.println(cs);
if (cs.mStartedOps.size() > 0) {
- pw.println(" Started ops:");
+ boolean printedStarted = false;
for (int j=0; j<cs.mStartedOps.size(); j++) {
Op op = cs.mStartedOps.get(j);
+ if (dumpOp >= 0 && op.op != dumpOp) {
+ continue;
+ }
+ if (!printedHeader) {
+ pw.println(" Clients:");
+ printedHeader = true;
+ }
+ if (!printedClient) {
+ pw.print(" "); pw.print(mClients.keyAt(i)); pw.println(":");
+ pw.print(" "); pw.println(cs);
+ printedClient = true;
+ }
+ if (!printedStarted) {
+ pw.println(" Started ops:");
+ printedStarted = true;
+ }
pw.print(" "); pw.print("uid="); pw.print(op.uid);
pw.print(" pkg="); pw.print(op.packageName);
pw.print(" op="); pw.println(AppOpsManager.opToName(op.op));
@@ -2774,7 +2898,7 @@
}
}
}
- if (mAudioRestrictions.size() > 0) {
+ if (mAudioRestrictions.size() > 0 && dumpOp < 0) {
boolean printedHeader = false;
for (int o=0; o<mAudioRestrictions.size(); o++) {
final String op = AppOpsManager.opToName(mAudioRestrictions.keyAt(o));
@@ -2789,7 +2913,7 @@
pw.print(" "); pw.print(op);
pw.print(" usage="); pw.print(AudioAttributes.usageToString(usage));
Restriction r = restrictions.valueAt(i);
- pw.print(": mode="); pw.println(MODE_NAMES[r.mode]);
+ pw.print(": mode="); pw.println(AppOpsManager.modeToName(r.mode));
if (!r.exceptionPackages.isEmpty()) {
pw.println(" Exceptions:");
for (int j=0; j<r.exceptionPackages.size(); j++) {
@@ -2804,38 +2928,83 @@
}
for (int i=0; i<mUidStates.size(); i++) {
UidState uidState = mUidStates.valueAt(i);
+ final SparseIntArray opModes = uidState.opModes;
+ final ArrayMap<String, Ops> pkgOps = uidState.pkgOps;
+
+ if (dumpOp >= 0) {
+ boolean hasOp = uidState.opModes != null
+ && uidState.opModes.indexOfKey(dumpOp) >= 0;
+ if (pkgOps != null) {
+ for (int pkgi = 0; !hasOp && pkgi < pkgOps.size(); pkgi++) {
+ Ops ops = pkgOps.valueAt(pkgi);
+ if (ops != null && ops.indexOfKey(dumpOp) >= 0) {
+ hasOp = true;
+ continue;
+ }
+ }
+ }
+ if (!hasOp) {
+ continue;
+ }
+ }
pw.print(" Uid "); UserHandle.formatUid(pw, uidState.uid); pw.println(":");
pw.print(" state=");
pw.println(UID_STATE_NAMES[uidState.state]);
+ if (uidState.state != uidState.pendingState) {
+ pw.print(" pendingState=");
+ pw.println(UID_STATE_NAMES[uidState.pendingState]);
+ }
+ if (uidState.pendingStateCommitTime != 0) {
+ pw.print(" pendingStateCommitTime=");
+ TimeUtils.formatDuration(uidState.pendingStateCommitTime, nowUptime, pw);
+ pw.println();
+ }
if (uidState.startNesting != 0) {
pw.print(" startNesting=");
pw.println(uidState.startNesting);
}
+ if (uidState.foregroundOps != null) {
+ pw.println(" foregroundOps:");
+ for (int j = 0; j < uidState.foregroundOps.size(); j++) {
+ pw.print(" ");
+ pw.println(AppOpsManager.opToName(uidState.foregroundOps.keyAt(j)));
+ }
+ }
needSep = true;
- SparseIntArray opModes = uidState.opModes;
if (opModes != null) {
final int opModeCount = opModes.size();
for (int j = 0; j < opModeCount; j++) {
final int code = opModes.keyAt(j);
final int mode = opModes.valueAt(j);
+ if (dumpOp >= 0 && dumpOp != code) {
+ continue;
+ }
pw.print(" "); pw.print(AppOpsManager.opToName(code));
- pw.print(": mode="); pw.println(MODE_NAMES[mode]);
+ pw.print(": mode="); pw.println(AppOpsManager.modeToName(mode));
}
}
- ArrayMap<String, Ops> pkgOps = uidState.pkgOps;
if (pkgOps == null) {
continue;
}
- for (Ops ops : pkgOps.values()) {
- pw.print(" Package "); pw.print(ops.packageName); pw.println(":");
+ for (int pkgi = 0; pkgi < pkgOps.size(); pkgi++) {
+ Ops ops = pkgOps.valueAt(pkgi);
+ boolean printedPackage = false;
for (int j=0; j<ops.size(); j++) {
Op op = ops.valueAt(j);
+ if (dumpOp >= 0 && dumpOp != op.op) {
+ continue;
+ }
+ if (!printedPackage) {
+ pw.print(" Package "); pw.print(ops.packageName); pw.println(":");
+ printedPackage = true;
+ }
pw.print(" "); pw.print(AppOpsManager.opToName(op.op));
- pw.print(" ("); pw.print(MODE_NAMES[op.mode]); pw.println("): ");
+ pw.print(" ("); pw.print(AppOpsManager.modeToName(op.mode));
+ pw.println("): ");
dumpTimesLocked(pw,
" Access: ",
" ", op.time, now, sdf, date);