Merge "Add concept of "paused" appOp (started, but MODE_IGNORED)" into sc-dev

This commit is contained in:
Nate Myren
2021-05-11 15:16:00 +00:00
committed by Android (Google) Code Review

View File

@@ -30,6 +30,7 @@ import static android.app.AppOpsManager.KEY_FG_SERVICE_STATE_SETTLE_TIME;
import static android.app.AppOpsManager.KEY_TOP_STATE_SETTLE_TIME;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_DEFAULT;
import static android.app.AppOpsManager.MODE_ERRORED;
import static android.app.AppOpsManager.MODE_FOREGROUND;
import static android.app.AppOpsManager.MODE_IGNORED;
import static android.app.AppOpsManager.NoteOpEvent;
@@ -827,6 +828,14 @@ public class AppOpsService extends IAppOpsService.Stub {
@GuardedBy("AppOpsService.this")
private @Nullable ArrayMap<IBinder, InProgressStartOpEvent> mInProgressEvents;
/**
* Currently paused startOp events
*
* <p>Key is clientId
*/
@GuardedBy("AppOpsService.this")
private @Nullable ArrayMap<IBinder, InProgressStartOpEvent> mPausedInProgressEvents;
AttributedOp(@Nullable String tag, @NonNull Op parent) {
this.tag = tag;
this.parent = parent;
@@ -944,23 +953,36 @@ public class AppOpsService extends IAppOpsService.Stub {
@Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
@AppOpsManager.UidState int uidState, @OpFlags int flags,
boolean triggerCallbackIfNeeded) throws RemoteException {
if (triggerCallbackIfNeeded && !parent.isRunning()) {
startedOrPaused(clientId, proxyUid, proxyPackageName, proxyAttributionTag,
uidState, flags, triggerCallbackIfNeeded, true);
}
private void startedOrPaused(@NonNull IBinder clientId, int proxyUid,
@Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
@AppOpsManager.UidState int uidState, @OpFlags int flags,
boolean triggerCallbackIfNeeded, boolean isStarted) throws RemoteException {
if (triggerCallbackIfNeeded && !parent.isRunning() && isStarted) {
scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
parent.packageName, true);
}
if (mInProgressEvents == null) {
if (isStarted && mInProgressEvents == null) {
mInProgressEvents = new ArrayMap<>(1);
} else if (mPausedInProgressEvents == null) {
mPausedInProgressEvents = new ArrayMap<>(1);
}
ArrayMap<IBinder, InProgressStartOpEvent> events = isStarted
? mInProgressEvents : mPausedInProgressEvents;
long startTime = System.currentTimeMillis();
InProgressStartOpEvent event = mInProgressEvents.get(clientId);
InProgressStartOpEvent event = events.get(clientId);
if (event == null) {
event = mInProgressStartOpEventPool.acquire(startTime,
SystemClock.elapsedRealtime(), clientId,
PooledLambda.obtainRunnable(AppOpsService::onClientDeath, this, clientId),
proxyUid, proxyPackageName, proxyAttributionTag, uidState, flags);
mInProgressEvents.put(clientId, event);
events.put(clientId, event);
} else {
if (uidState != event.mUidState) {
onUidStateChanged(uidState);
@@ -969,12 +991,15 @@ public class AppOpsService extends IAppOpsService.Stub {
event.numUnfinishedStarts++;
mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, parent.packageName,
tag, uidState, flags, startTime);
if (isStarted) {
mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
parent.packageName, tag, uidState, flags, startTime);
}
}
/**
* Update state when finishOp was called
* Update state when finishOp was called. Will finish started ops, and delete paused ops.
*
* @param clientId Id of the finishOp caller
*/
@@ -983,22 +1008,32 @@ public class AppOpsService extends IAppOpsService.Stub {
}
private void finished(@NonNull IBinder clientId, boolean triggerCallbackIfNeeded) {
if (mInProgressEvents == null) {
Slog.wtf(TAG, "No ops running");
return;
}
finishOrPause(clientId, triggerCallbackIfNeeded, false);
}
int indexOfToken = mInProgressEvents.indexOfKey(clientId);
/**
* Update state when paused or finished is called. If pausing, it records the op as
* stopping in the HistoricalRegistry, but does not delete it.
*/
private void finishOrPause(@NonNull IBinder clientId, boolean triggerCallbackIfNeeded,
boolean isPausing) {
int indexOfToken = mInProgressEvents != null
? mInProgressEvents.indexOfKey(clientId) : -1;
if (indexOfToken < 0) {
Slog.wtf(TAG, "No op running for the client");
finishPossiblyPaused(clientId, isPausing);
return;
}
InProgressStartOpEvent event = mInProgressEvents.valueAt(indexOfToken);
event.numUnfinishedStarts--;
if (event.numUnfinishedStarts == 0) {
event.finish();
mInProgressEvents.removeAt(indexOfToken);
if (!isPausing) {
event.numUnfinishedStarts--;
}
// If we are pausing, create a NoteOpEvent, but don't change the InProgress event
if (event.numUnfinishedStarts == 0 || isPausing) {
if (!isPausing) {
event.finish();
mInProgressEvents.removeAt(indexOfToken);
}
if (mAccessEvents == null) {
mAccessEvents = new LongSparseArray<>(1);
@@ -1018,20 +1053,112 @@ public class AppOpsService extends IAppOpsService.Stub {
parent.packageName, tag, event.getUidState(),
event.getFlags(), finishedEvent.getNoteTime(), finishedEvent.getDuration());
mInProgressStartOpEventPool.release(event);
if (!isPausing) {
mInProgressStartOpEventPool.release(event);
if (mInProgressEvents.isEmpty()) {
mInProgressEvents = null;
if (mInProgressEvents.isEmpty()) {
mInProgressEvents = null;
// TODO moltmann: Also callback for single attribution tag activity changes
if (triggerCallbackIfNeeded && !parent.isRunning()) {
scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
parent.packageName, false);
// TODO ntmyren: Also callback for single attribution tag activity changes
if (triggerCallbackIfNeeded && !parent.isRunning()) {
scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
parent.packageName, false);
}
}
}
}
}
// Finish or pause (no-op) an already paused op
private void finishPossiblyPaused(@NonNull IBinder clientId, boolean isPausing) {
if (mPausedInProgressEvents == null) {
Slog.wtf(TAG, "No ops running or paused");
return;
}
int indexOfToken = mPausedInProgressEvents.indexOfKey(clientId);
if (indexOfToken < 0) {
Slog.wtf(TAG, "No op running or paused for the client");
return;
} else if (isPausing) {
// already paused
return;
}
// no need to record a paused event finishing.
InProgressStartOpEvent event = mInProgressEvents.valueAt(indexOfToken);
event.numUnfinishedStarts--;
if (event.numUnfinishedStarts == 0) {
mPausedInProgressEvents.removeAt(indexOfToken);
mInProgressStartOpEventPool.release(event);
if (mPausedInProgressEvents.isEmpty()) {
mPausedInProgressEvents = null;
}
}
}
/**
* Create an event that will be started, if the op is unpaused.
*/
public void createPaused(@NonNull IBinder clientId, int proxyUid,
@Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
@AppOpsManager.UidState int uidState, @OpFlags int flags) throws RemoteException {
startedOrPaused(clientId, proxyUid, proxyPackageName, proxyAttributionTag,
uidState, flags, true, false);
}
/**
* Pause all currently started ops. This will create a HistoricalRegistry
*/
public void pause() {
if (mInProgressEvents == null) {
return;
}
if (mPausedInProgressEvents == null) {
mPausedInProgressEvents = new ArrayMap<>(1);
}
for (int i = 0; i < mInProgressEvents.size(); i++) {
InProgressStartOpEvent event = mInProgressEvents.valueAt(i);
mPausedInProgressEvents.put(event.mClientId, event);
finishOrPause(event.mClientId, true, true);
}
scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
parent.packageName, false);
mInProgressEvents = null;
}
/**
* Unpause all currently paused ops. This will reinitialize their start and duration
* times, but keep all other values the same
*/
public void resume() {
if (mPausedInProgressEvents == null) {
return;
}
if (mInProgressEvents == null) {
mInProgressEvents = new ArrayMap<>(mPausedInProgressEvents.size());
}
boolean shouldSendActive = !mPausedInProgressEvents.isEmpty()
&& mInProgressEvents.isEmpty();
long startTime = System.currentTimeMillis();
for (int i = 0; i < mPausedInProgressEvents.size(); i++) {
InProgressStartOpEvent event = mPausedInProgressEvents.valueAt(i);
mInProgressEvents.put(event.mClientId, event);
event.mStartElapsedTime = SystemClock.elapsedRealtime();
event.mStartTime = startTime;
mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
parent.packageName, tag, event.mUidState, event.mFlags, startTime);
}
if (shouldSendActive) {
scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
parent.packageName, true);
}
mPausedInProgressEvents = null;
}
/**
* Called in the case the client dies without calling finish first
*
@@ -2303,6 +2430,9 @@ public class AppOpsService extends IAppOpsService.Stub {
scheduleWriteLocked();
}
uidState.evalForegroundOps(mOpModeWatchers);
if (mode != MODE_ERRORED && mode != previousMode) {
updateStartedOpModeForUidLocked(code, mode == MODE_IGNORED, uid);
}
}
notifyOpChangedForAllPkgsInUid(code, uid, false, permissionPolicyCallback);
@@ -2556,6 +2686,9 @@ public class AppOpsService extends IAppOpsService.Stub {
pruneOpLocked(op, uid, packageName);
}
scheduleFastWriteLocked();
if (mode != MODE_ERRORED) {
updateStartedOpModeForUidLocked(code, mode == MODE_IGNORED, uid);
}
}
}
}
@@ -3660,6 +3793,7 @@ public class AppOpsService extends IAppOpsService.Stub {
packageName);
}
boolean isRestricted = false;
synchronized (this) {
final Ops ops = getOpsLocked(uid, packageName, attributionTag, bypass, true /* edit */);
if (ops == null) {
@@ -3673,18 +3807,10 @@ public class AppOpsService extends IAppOpsService.Stub {
packageName);
}
final Op op = getOpLocked(ops, code, uid, true);
if (isOpRestrictedLocked(uid, code, packageName, bypass)) {
if (!dryRun) {
scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
flags, AppOpsManager.MODE_IGNORED);
}
return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag,
packageName);
}
final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag);
final int switchCode = AppOpsManager.opToSwitch(code);
final UidState uidState = ops.uidState;
isRestricted = isOpRestrictedLocked(uid, code, packageName, bypass);
final int switchCode = AppOpsManager.opToSwitch(code);
// 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) {
@@ -3720,25 +3846,30 @@ public class AppOpsService extends IAppOpsService.Stub {
}
}
if (DEBUG) Slog.d(TAG, "startOperation: allowing code " + code + " uid " + uid
+ " package " + packageName);
+ " package " + packageName + " restricted: " + isRestricted);
if (!dryRun) {
scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, flags,
AppOpsManager.MODE_ALLOWED);
try {
attributedOp.started(clientId, proxyUid, proxyPackageName, proxyAttributionTag,
uidState.state, flags);
if (isRestricted) {
attributedOp.createPaused(clientId, proxyUid, proxyPackageName,
proxyAttributionTag, uidState.state, flags);
} else {
attributedOp.started(clientId, proxyUid, proxyPackageName,
proxyAttributionTag, uidState.state, flags);
}
} catch (RemoteException e) {
throw new RuntimeException(e);
}
scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, flags,
isRestricted ? MODE_IGNORED : MODE_ALLOWED);
}
}
if (shouldCollectAsyncNotedOp && !dryRun) {
if (shouldCollectAsyncNotedOp && !dryRun && !isRestricted) {
collectAsyncNotedOp(uid, packageName, code, attributionTag, AppOpsManager.OP_FLAG_SELF,
message, shouldCollectMessage);
}
return new SyncNotedAppOp(AppOpsManager.MODE_ALLOWED, code, attributionTag,
return new SyncNotedAppOp(isRestricted ? MODE_IGNORED : MODE_ALLOWED, code, attributionTag,
packageName);
}
@@ -6116,6 +6247,9 @@ public class AppOpsService extends IAppOpsService.Stub {
if (restrictionState.setRestriction(code, restricted, exceptionPackages, userHandle)) {
mHandler.sendMessage(PooledLambda.obtainMessage(
AppOpsService::notifyWatchersOfChange, this, code, UID_ANY));
mHandler.sendMessage(PooledLambda.obtainMessage(
AppOpsService::updateStartedOpModeForUser, this, code, restricted,
userHandle));
}
if (restrictionState.isDefault()) {
@@ -6125,6 +6259,44 @@ public class AppOpsService extends IAppOpsService.Stub {
}
}
private void updateStartedOpModeForUser(int code, boolean restricted, int userId) {
synchronized (AppOpsService.this) {
int numUids = mUidStates.size();
for (int uidNum = 0; uidNum < numUids; uidNum++) {
int uid = mUidStates.keyAt(uidNum);
if (userId != UserHandle.USER_ALL && UserHandle.getUserId(uid) != userId) {
continue;
}
updateStartedOpModeForUidLocked(code, restricted, uid);
}
}
}
private void updateStartedOpModeForUidLocked(int code, boolean restricted, int uid) {
UidState uidState = mUidStates.get(uid);
if (uidState == null || uidState.pkgOps == null) {
return;
}
int numPkgOps = uidState.pkgOps.size();
for (int pkgNum = 0; pkgNum < numPkgOps; pkgNum++) {
Ops ops = uidState.pkgOps.valueAt(pkgNum);
Op op = ops != null ? ops.get(code) : null;
if (op == null || (op.mode != MODE_ALLOWED && op.mode != MODE_FOREGROUND)) {
continue;
}
int numAttrTags = op.mAttributions.size();
for (int attrNum = 0; attrNum < numAttrTags; attrNum++) {
AttributedOp attrOp = op.mAttributions.valueAt(attrNum);
if (restricted) {
attrOp.pause();
} else {
attrOp.resume();
}
}
}
}
private void notifyWatchersOfChange(int code, int uid) {
final ArraySet<ModeCallback> clonedCallbacks;
synchronized (this) {