Refactor: move AttributedOp out of AppOpsService
Currently, AttributedOp is an inner class of AppOpsService. Use the "Move Inner Class to Upper Level" refactor tool to to make it a top-level class. Test: presubmit Change-Id: I3cfe46ea205b944a500c1a877e71d4571a5a8ed9
This commit is contained in:
@@ -33,7 +33,6 @@ 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;
|
||||
import static android.app.AppOpsManager.OP_CAMERA;
|
||||
import static android.app.AppOpsManager.OP_FLAGS_ALL;
|
||||
import static android.app.AppOpsManager.OP_FLAG_SELF;
|
||||
@@ -45,7 +44,6 @@ import static android.app.AppOpsManager.OP_RECORD_AUDIO;
|
||||
import static android.app.AppOpsManager.OP_RECORD_AUDIO_HOTWORD;
|
||||
import static android.app.AppOpsManager.OP_VIBRATE;
|
||||
import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_FAILED;
|
||||
import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_RESUMED;
|
||||
import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_STARTED;
|
||||
import static android.app.AppOpsManager.OpEventProxyInfo;
|
||||
import static android.app.AppOpsManager.RestrictionBypass;
|
||||
@@ -57,7 +55,6 @@ import static android.app.AppOpsManager.SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTIO
|
||||
import static android.app.AppOpsManager._NUM_OP;
|
||||
import static android.app.AppOpsManager.extractFlagsFromKey;
|
||||
import static android.app.AppOpsManager.extractUidStateFromKey;
|
||||
import static android.app.AppOpsManager.makeKey;
|
||||
import static android.app.AppOpsManager.modeToName;
|
||||
import static android.app.AppOpsManager.opAllowSystemBypassRestriction;
|
||||
import static android.app.AppOpsManager.opRestrictsRead;
|
||||
@@ -131,7 +128,6 @@ import android.util.ArraySet;
|
||||
import android.util.AtomicFile;
|
||||
import android.util.IndentingPrintWriter;
|
||||
import android.util.KeyValueListParser;
|
||||
import android.util.LongSparseArray;
|
||||
import android.util.Pair;
|
||||
import android.util.Pools;
|
||||
import android.util.Pools.SimplePool;
|
||||
@@ -827,578 +823,6 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch
|
||||
}
|
||||
}
|
||||
|
||||
final class AttributedOp {
|
||||
private final @NonNull AppOpsService mAppOpsService;
|
||||
public final @Nullable String tag;
|
||||
public final @NonNull Op parent;
|
||||
|
||||
/**
|
||||
* Last successful accesses (noteOp + finished startOp) for each uidState/opFlag combination
|
||||
*
|
||||
* <p>Key is {@link AppOpsManager#makeKey}
|
||||
*/
|
||||
@GuardedBy("AppOpsService.this")
|
||||
private @Nullable LongSparseArray<NoteOpEvent> mAccessEvents;
|
||||
|
||||
/**
|
||||
* Last rejected accesses for each uidState/opFlag combination
|
||||
*
|
||||
* <p>Key is {@link AppOpsManager#makeKey}
|
||||
*/
|
||||
@GuardedBy("AppOpsService.this")
|
||||
private @Nullable LongSparseArray<NoteOpEvent> mRejectEvents;
|
||||
|
||||
/**
|
||||
* Currently in progress startOp events
|
||||
*
|
||||
* <p>Key is clientId
|
||||
*/
|
||||
@GuardedBy("AppOpsService.this")
|
||||
@Nullable ArrayMap<IBinder, InProgressStartOpEvent> mInProgressEvents;
|
||||
|
||||
/**
|
||||
* Currently paused startOp events
|
||||
*
|
||||
* <p>Key is clientId
|
||||
*/
|
||||
@GuardedBy("AppOpsService.this")
|
||||
@Nullable ArrayMap<IBinder, InProgressStartOpEvent> mPausedInProgressEvents;
|
||||
|
||||
AttributedOp(@NonNull AppOpsService appOpsService, @Nullable String tag,
|
||||
@NonNull Op parent) {
|
||||
this.mAppOpsService = appOpsService;
|
||||
this.tag = tag;
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update state when noteOp was rejected or startOp->finishOp event finished
|
||||
*
|
||||
* @param proxyUid The uid of the proxy
|
||||
* @param proxyPackageName The package name of the proxy
|
||||
* @param proxyAttributionTag the attributionTag in the proxies package
|
||||
* @param uidState UID state of the app noteOp/startOp was called for
|
||||
* @param flags OpFlags of the call
|
||||
*/
|
||||
public void accessed(int proxyUid, @Nullable String proxyPackageName,
|
||||
@Nullable String proxyAttributionTag, @AppOpsManager.UidState int uidState,
|
||||
@OpFlags int flags) {
|
||||
long accessTime = System.currentTimeMillis();
|
||||
accessed(accessTime, -1, proxyUid, proxyPackageName,
|
||||
proxyAttributionTag, uidState, flags);
|
||||
|
||||
mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
|
||||
parent.packageName, tag, uidState, flags, accessTime,
|
||||
AppOpsManager.ATTRIBUTION_FLAGS_NONE, AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an access that was previously collected.
|
||||
*
|
||||
* @param noteTime The time of the event
|
||||
* @param duration The duration of the event
|
||||
* @param proxyUid The uid of the proxy
|
||||
* @param proxyPackageName The package name of the proxy
|
||||
* @param proxyAttributionTag the attributionTag in the proxies package
|
||||
* @param uidState UID state of the app noteOp/startOp was called for
|
||||
* @param flags OpFlags of the call
|
||||
*/
|
||||
public void accessed(long noteTime, long duration, int proxyUid,
|
||||
@Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
|
||||
@AppOpsManager.UidState int uidState, @OpFlags int flags) {
|
||||
long key = makeKey(uidState, flags);
|
||||
|
||||
if (mAccessEvents == null) {
|
||||
mAccessEvents = new LongSparseArray<>(1);
|
||||
}
|
||||
|
||||
OpEventProxyInfo proxyInfo = null;
|
||||
if (proxyUid != Process.INVALID_UID) {
|
||||
proxyInfo = mAppOpsService.mOpEventProxyInfoPool.acquire(proxyUid, proxyPackageName,
|
||||
proxyAttributionTag);
|
||||
}
|
||||
|
||||
NoteOpEvent existingEvent = mAccessEvents.get(key);
|
||||
if (existingEvent != null) {
|
||||
existingEvent.reinit(noteTime, duration, proxyInfo,
|
||||
mAppOpsService.mOpEventProxyInfoPool);
|
||||
} else {
|
||||
mAccessEvents.put(key, new NoteOpEvent(noteTime, duration, proxyInfo));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update state when noteOp/startOp was rejected.
|
||||
*
|
||||
* @param uidState UID state of the app noteOp is called for
|
||||
* @param flags OpFlags of the call
|
||||
*/
|
||||
public void rejected(@AppOpsManager.UidState int uidState, @OpFlags int flags) {
|
||||
rejected(System.currentTimeMillis(), uidState, flags);
|
||||
|
||||
mAppOpsService.mHistoricalRegistry.incrementOpRejected(parent.op, parent.uid,
|
||||
parent.packageName, tag, uidState, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an rejection that was previously collected
|
||||
*
|
||||
* @param noteTime The time of the event
|
||||
* @param uidState UID state of the app noteOp/startOp was called for
|
||||
* @param flags OpFlags of the call
|
||||
*/
|
||||
public void rejected(long noteTime, @AppOpsManager.UidState int uidState,
|
||||
@OpFlags int flags) {
|
||||
long key = makeKey(uidState, flags);
|
||||
|
||||
if (mRejectEvents == null) {
|
||||
mRejectEvents = new LongSparseArray<>(1);
|
||||
}
|
||||
|
||||
// We do not collect proxy information for rejections yet
|
||||
NoteOpEvent existingEvent = mRejectEvents.get(key);
|
||||
if (existingEvent != null) {
|
||||
existingEvent.reinit(noteTime, -1, null, mAppOpsService.mOpEventProxyInfoPool);
|
||||
} else {
|
||||
mRejectEvents.put(key, new NoteOpEvent(noteTime, -1, null));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update state when start was called
|
||||
*
|
||||
* @param clientId Id of the startOp caller
|
||||
* @param proxyUid The UID of the proxy app
|
||||
* @param proxyPackageName The package name of the proxy app
|
||||
* @param proxyAttributionTag The attribution tag of the proxy app
|
||||
* @param uidState UID state of the app startOp is called for
|
||||
* @param flags The proxy flags
|
||||
* @param attributionFlags The attribution flags associated with this operation.
|
||||
* @param attributionChainId The if of the attribution chain this operations is a part of.
|
||||
*/
|
||||
public void started(@NonNull IBinder clientId, int proxyUid,
|
||||
@Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
|
||||
@AppOpsManager.UidState int uidState, @OpFlags int flags, @AttributionFlags
|
||||
int attributionFlags, int attributionChainId) throws RemoteException {
|
||||
started(clientId, proxyUid, proxyPackageName, proxyAttributionTag,
|
||||
uidState, flags,/*triggerCallbackIfNeeded*/ true, attributionFlags,
|
||||
attributionChainId);
|
||||
}
|
||||
|
||||
private void started(@NonNull IBinder clientId, int proxyUid,
|
||||
@Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
|
||||
@AppOpsManager.UidState int uidState, @OpFlags int flags,
|
||||
boolean triggerCallbackIfNeeded, @AttributionFlags int attributionFlags,
|
||||
int attributionChainId) throws RemoteException {
|
||||
startedOrPaused(clientId, proxyUid, proxyPackageName,
|
||||
proxyAttributionTag, uidState, flags, triggerCallbackIfNeeded,
|
||||
/*triggerCallbackIfNeeded*/ true, attributionFlags, attributionChainId);
|
||||
}
|
||||
|
||||
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, @AttributionFlags
|
||||
int attributionFlags, int attributionChainId) throws RemoteException {
|
||||
if (triggerCallbackIfNeeded && !parent.isRunning() && isStarted) {
|
||||
mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
|
||||
parent.packageName, tag, true, attributionFlags, attributionChainId);
|
||||
}
|
||||
|
||||
if (isStarted && mInProgressEvents == null) {
|
||||
mInProgressEvents = new ArrayMap<>(1);
|
||||
} else if (!isStarted && mPausedInProgressEvents == null) {
|
||||
mPausedInProgressEvents = new ArrayMap<>(1);
|
||||
}
|
||||
ArrayMap<IBinder, InProgressStartOpEvent> events = isStarted
|
||||
? mInProgressEvents : mPausedInProgressEvents;
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
InProgressStartOpEvent event = events.get(clientId);
|
||||
if (event == null) {
|
||||
event = mAppOpsService.mInProgressStartOpEventPool.acquire(startTime,
|
||||
SystemClock.elapsedRealtime(), clientId, tag,
|
||||
PooledLambda.obtainRunnable(AppOpsService::onClientDeath, this, clientId),
|
||||
proxyUid, proxyPackageName, proxyAttributionTag, uidState, flags,
|
||||
attributionFlags, attributionChainId);
|
||||
events.put(clientId, event);
|
||||
} else {
|
||||
if (uidState != event.getUidState()) {
|
||||
onUidStateChanged(uidState);
|
||||
}
|
||||
}
|
||||
|
||||
event.numUnfinishedStarts++;
|
||||
|
||||
if (isStarted) {
|
||||
mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
|
||||
parent.packageName, tag, uidState, flags, startTime, attributionFlags,
|
||||
attributionChainId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update state when finishOp was called. Will finish started ops, and delete paused ops.
|
||||
*
|
||||
* @param clientId Id of the finishOp caller
|
||||
*/
|
||||
public void finished(@NonNull IBinder clientId) {
|
||||
finished(clientId, true);
|
||||
}
|
||||
|
||||
private void finished(@NonNull IBinder clientId, boolean triggerCallbackIfNeeded) {
|
||||
finishOrPause(clientId, triggerCallbackIfNeeded, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = isRunning() ? mInProgressEvents.indexOfKey(clientId) : -1;
|
||||
if (indexOfToken < 0) {
|
||||
finishPossiblyPaused(clientId, isPausing);
|
||||
return;
|
||||
}
|
||||
|
||||
InProgressStartOpEvent event = mInProgressEvents.valueAt(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);
|
||||
}
|
||||
|
||||
OpEventProxyInfo proxyCopy = event.getProxy() != null
|
||||
? new OpEventProxyInfo(event.getProxy()) : null;
|
||||
|
||||
long accessDurationMillis =
|
||||
SystemClock.elapsedRealtime() - event.getStartElapsedTime();
|
||||
NoteOpEvent finishedEvent = new NoteOpEvent(event.getStartTime(),
|
||||
accessDurationMillis, proxyCopy);
|
||||
mAccessEvents.put(makeKey(event.getUidState(), event.getFlags()),
|
||||
finishedEvent);
|
||||
|
||||
mAppOpsService.mHistoricalRegistry.increaseOpAccessDuration(parent.op, parent.uid,
|
||||
parent.packageName, tag, event.getUidState(),
|
||||
event.getFlags(), finishedEvent.getNoteTime(), finishedEvent.getDuration(),
|
||||
event.getAttributionFlags(), event.getAttributionChainId());
|
||||
|
||||
if (!isPausing) {
|
||||
mAppOpsService.mInProgressStartOpEventPool.release(event);
|
||||
if (mInProgressEvents.isEmpty()) {
|
||||
mInProgressEvents = null;
|
||||
|
||||
// TODO ntmyren: Also callback for single attribution tag activity changes
|
||||
if (triggerCallbackIfNeeded && !parent.isRunning()) {
|
||||
mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op,
|
||||
parent.uid, parent.packageName, tag, false,
|
||||
event.getAttributionFlags(), event.getAttributionChainId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Finish or pause (no-op) an already paused op
|
||||
private void finishPossiblyPaused(@NonNull IBinder clientId, boolean isPausing) {
|
||||
if (!isPaused()) {
|
||||
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 = mPausedInProgressEvents.valueAt(indexOfToken);
|
||||
event.numUnfinishedStarts--;
|
||||
if (event.numUnfinishedStarts == 0) {
|
||||
mPausedInProgressEvents.removeAt(indexOfToken);
|
||||
mAppOpsService.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, @AttributionFlags
|
||||
int attributionFlags, int attributionChainId) throws RemoteException {
|
||||
startedOrPaused(clientId, proxyUid, proxyPackageName, proxyAttributionTag,
|
||||
uidState, flags, true, false, attributionFlags, attributionChainId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pause all currently started ops. This will create a HistoricalRegistry
|
||||
*/
|
||||
public void pause() {
|
||||
if (!isRunning()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mPausedInProgressEvents == null) {
|
||||
mPausedInProgressEvents = new ArrayMap<>(1);
|
||||
}
|
||||
|
||||
for (int i = 0; i < mInProgressEvents.size(); i++) {
|
||||
InProgressStartOpEvent event = mInProgressEvents.valueAt(i);
|
||||
mPausedInProgressEvents.put(event.getClientId(), event);
|
||||
finishOrPause(event.getClientId(), true, true);
|
||||
|
||||
mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
|
||||
parent.packageName, tag, false,
|
||||
event.getAttributionFlags(), event.getAttributionChainId());
|
||||
}
|
||||
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 (!isPaused()) {
|
||||
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.getClientId(), event);
|
||||
event.setStartElapsedTime(SystemClock.elapsedRealtime());
|
||||
event.setStartTime(startTime);
|
||||
mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
|
||||
parent.packageName, tag, event.getUidState(), event.getFlags(), startTime,
|
||||
event.getAttributionFlags(), event.getAttributionChainId());
|
||||
if (shouldSendActive) {
|
||||
mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
|
||||
parent.packageName, tag, true, event.getAttributionFlags(),
|
||||
event.getAttributionChainId());
|
||||
}
|
||||
// Note: this always sends MODE_ALLOWED, even if the mode is FOREGROUND
|
||||
// TODO ntmyren: figure out how to get the real mode.
|
||||
mAppOpsService.scheduleOpStartedIfNeededLocked(parent.op, parent.uid,
|
||||
parent.packageName, tag, event.getFlags(), MODE_ALLOWED, START_TYPE_RESUMED,
|
||||
event.getAttributionFlags(), event.getAttributionChainId());
|
||||
}
|
||||
mPausedInProgressEvents = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called in the case the client dies without calling finish first
|
||||
*
|
||||
* @param clientId The client that died
|
||||
*/
|
||||
void onClientDeath(@NonNull IBinder clientId) {
|
||||
synchronized (mAppOpsService) {
|
||||
if (!isPaused() && !isRunning()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ArrayMap<IBinder, InProgressStartOpEvent> events = isPaused()
|
||||
? mPausedInProgressEvents : mInProgressEvents;
|
||||
InProgressStartOpEvent deadEvent = events.get(clientId);
|
||||
if (deadEvent != null) {
|
||||
deadEvent.numUnfinishedStarts = 1;
|
||||
}
|
||||
|
||||
finished(clientId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify that the state of the uid changed
|
||||
*
|
||||
* @param newState The new state
|
||||
*/
|
||||
public void onUidStateChanged(@AppOpsManager.UidState int newState) {
|
||||
if (!isPaused() && !isRunning()) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean isRunning = isRunning();
|
||||
ArrayMap<IBinder, AppOpsService.InProgressStartOpEvent> events =
|
||||
isRunning ? mInProgressEvents : mPausedInProgressEvents;
|
||||
|
||||
int numInProgressEvents = events.size();
|
||||
List<IBinder> binders = new ArrayList<>(events.keySet());
|
||||
for (int i = 0; i < numInProgressEvents; i++) {
|
||||
InProgressStartOpEvent event = events.get(binders.get(i));
|
||||
|
||||
if (event != null && event.getUidState() != newState) {
|
||||
try {
|
||||
// Remove all but one unfinished start count and then call finished() to
|
||||
// remove start event object
|
||||
int numPreviousUnfinishedStarts = event.numUnfinishedStarts;
|
||||
event.numUnfinishedStarts = 1;
|
||||
OpEventProxyInfo proxy = event.getProxy();
|
||||
|
||||
finished(event.getClientId(), false);
|
||||
|
||||
// Call started() to add a new start event object and then add the
|
||||
// previously removed unfinished start counts back
|
||||
if (proxy != null) {
|
||||
startedOrPaused(event.getClientId(), proxy.getUid(),
|
||||
proxy.getPackageName(), proxy.getAttributionTag(), newState,
|
||||
event.getFlags(), false, isRunning,
|
||||
event.getAttributionFlags(), event.getAttributionChainId());
|
||||
} else {
|
||||
startedOrPaused(event.getClientId(), Process.INVALID_UID, null, null,
|
||||
newState, event.getFlags(), false, isRunning,
|
||||
event.getAttributionFlags(), event.getAttributionChainId());
|
||||
}
|
||||
|
||||
events = isRunning ? mInProgressEvents : mPausedInProgressEvents;
|
||||
InProgressStartOpEvent newEvent = events.get(binders.get(i));
|
||||
if (newEvent != null) {
|
||||
newEvent.numUnfinishedStarts += numPreviousUnfinishedStarts - 1;
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
if (DEBUG) Slog.e(TAG, "Cannot switch to new uidState " + newState);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine {@code a} and {@code b} and return the result. The result might be {@code a}
|
||||
* or {@code b}. If there is an event for the same key in both the later event is retained.
|
||||
*/
|
||||
private @Nullable LongSparseArray<NoteOpEvent> add(@Nullable LongSparseArray<NoteOpEvent> a,
|
||||
@Nullable LongSparseArray<NoteOpEvent> b) {
|
||||
if (a == null) {
|
||||
return b;
|
||||
}
|
||||
|
||||
if (b == null) {
|
||||
return a;
|
||||
}
|
||||
|
||||
int numEventsToAdd = b.size();
|
||||
for (int i = 0; i < numEventsToAdd; i++) {
|
||||
long keyOfEventToAdd = b.keyAt(i);
|
||||
NoteOpEvent bEvent = b.valueAt(i);
|
||||
NoteOpEvent aEvent = a.get(keyOfEventToAdd);
|
||||
|
||||
if (aEvent == null || bEvent.getNoteTime() > aEvent.getNoteTime()) {
|
||||
a.put(keyOfEventToAdd, bEvent);
|
||||
}
|
||||
}
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all data from the {@code opToAdd} to this op.
|
||||
*
|
||||
* <p>If there is an event for the same key in both the later event is retained.
|
||||
* <p>{@code opToAdd} should not be used after this method is called.
|
||||
*
|
||||
* @param opToAdd The op to add
|
||||
*/
|
||||
public void add(@NonNull AttributedOp opToAdd) {
|
||||
if (opToAdd.isRunning() || opToAdd.isPaused()) {
|
||||
ArrayMap<IBinder, InProgressStartOpEvent> ignoredEvents = opToAdd.isRunning()
|
||||
? opToAdd.mInProgressEvents : opToAdd.mPausedInProgressEvents;
|
||||
Slog.w(TAG, "Ignoring " + ignoredEvents.size() + " app-ops, running: "
|
||||
+ opToAdd.isRunning());
|
||||
|
||||
int numInProgressEvents = ignoredEvents.size();
|
||||
for (int i = 0; i < numInProgressEvents; i++) {
|
||||
InProgressStartOpEvent event = ignoredEvents.valueAt(i);
|
||||
|
||||
event.finish();
|
||||
mAppOpsService.mInProgressStartOpEventPool.release(event);
|
||||
}
|
||||
}
|
||||
|
||||
mAccessEvents = add(mAccessEvents, opToAdd.mAccessEvents);
|
||||
mRejectEvents = add(mRejectEvents, opToAdd.mRejectEvents);
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
return mInProgressEvents != null && !mInProgressEvents.isEmpty();
|
||||
}
|
||||
|
||||
public boolean isPaused() {
|
||||
return mPausedInProgressEvents != null && !mPausedInProgressEvents.isEmpty();
|
||||
}
|
||||
|
||||
boolean hasAnyTime() {
|
||||
return (mAccessEvents != null && mAccessEvents.size() > 0)
|
||||
|| (mRejectEvents != null && mRejectEvents.size() > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone a {@link LongSparseArray} and clone all values.
|
||||
*/
|
||||
private @Nullable LongSparseArray<NoteOpEvent> deepClone(
|
||||
@Nullable LongSparseArray<NoteOpEvent> original) {
|
||||
if (original == null) {
|
||||
return original;
|
||||
}
|
||||
|
||||
int size = original.size();
|
||||
LongSparseArray<NoteOpEvent> clone = new LongSparseArray<>(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
clone.put(original.keyAt(i), new NoteOpEvent(original.valueAt(i)));
|
||||
}
|
||||
|
||||
return clone;
|
||||
}
|
||||
|
||||
@NonNull AttributedOpEntry createAttributedOpEntryLocked() {
|
||||
LongSparseArray<NoteOpEvent> accessEvents = deepClone(mAccessEvents);
|
||||
|
||||
// Add in progress events as access events
|
||||
if (isRunning()) {
|
||||
long now = SystemClock.elapsedRealtime();
|
||||
int numInProgressEvents = mInProgressEvents.size();
|
||||
|
||||
if (accessEvents == null) {
|
||||
accessEvents = new LongSparseArray<>(numInProgressEvents);
|
||||
}
|
||||
|
||||
for (int i = 0; i < numInProgressEvents; i++) {
|
||||
InProgressStartOpEvent event = mInProgressEvents.valueAt(i);
|
||||
|
||||
accessEvents.append(makeKey(event.getUidState(), event.getFlags()),
|
||||
new NoteOpEvent(event.getStartTime(), now - event.getStartElapsedTime(),
|
||||
event.getProxy()));
|
||||
}
|
||||
}
|
||||
|
||||
LongSparseArray<NoteOpEvent> rejectEvents = deepClone(mRejectEvents);
|
||||
|
||||
return new AttributedOpEntry(parent.op, isRunning(), accessEvents, rejectEvents);
|
||||
}
|
||||
}
|
||||
|
||||
final class Op {
|
||||
int op;
|
||||
int uid;
|
||||
|
||||
626
services/core/java/com/android/server/appop/AttributedOp.java
Normal file
626
services/core/java/com/android/server/appop/AttributedOp.java
Normal file
@@ -0,0 +1,626 @@
|
||||
/*
|
||||
* 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.OnOpStartedListener.START_TYPE_RESUMED;
|
||||
import static android.app.AppOpsManager.makeKey;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.app.AppOpsManager;
|
||||
import android.os.IBinder;
|
||||
import android.os.Process;
|
||||
import android.os.RemoteException;
|
||||
import android.os.SystemClock;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.LongSparseArray;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.util.function.pooled.PooledLambda;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
final class AttributedOp {
|
||||
private final @NonNull AppOpsService mAppOpsService;
|
||||
public final @Nullable String tag;
|
||||
public final @NonNull AppOpsService.Op parent;
|
||||
|
||||
/**
|
||||
* Last successful accesses (noteOp + finished startOp) for each uidState/opFlag combination
|
||||
*
|
||||
* <p>Key is {@link AppOpsManager#makeKey}
|
||||
*/
|
||||
@GuardedBy("mAppOpsService")
|
||||
private @Nullable LongSparseArray<AppOpsManager.NoteOpEvent> mAccessEvents;
|
||||
|
||||
/**
|
||||
* Last rejected accesses for each uidState/opFlag combination
|
||||
*
|
||||
* <p>Key is {@link AppOpsManager#makeKey}
|
||||
*/
|
||||
@GuardedBy("mAppOpsService")
|
||||
private @Nullable LongSparseArray<AppOpsManager.NoteOpEvent> mRejectEvents;
|
||||
|
||||
/**
|
||||
* Currently in progress startOp events
|
||||
*
|
||||
* <p>Key is clientId
|
||||
*/
|
||||
@GuardedBy("mAppOpsService")
|
||||
@Nullable ArrayMap<IBinder, AppOpsService.InProgressStartOpEvent> mInProgressEvents;
|
||||
|
||||
/**
|
||||
* Currently paused startOp events
|
||||
*
|
||||
* <p>Key is clientId
|
||||
*/
|
||||
@GuardedBy("mAppOpsService")
|
||||
@Nullable ArrayMap<IBinder, AppOpsService.InProgressStartOpEvent> mPausedInProgressEvents;
|
||||
|
||||
AttributedOp(@NonNull AppOpsService appOpsService, @Nullable String tag,
|
||||
@NonNull AppOpsService.Op parent) {
|
||||
mAppOpsService = appOpsService;
|
||||
this.tag = tag;
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update state when noteOp was rejected or startOp->finishOp event finished
|
||||
*
|
||||
* @param proxyUid The uid of the proxy
|
||||
* @param proxyPackageName The package name of the proxy
|
||||
* @param proxyAttributionTag the attributionTag in the proxies package
|
||||
* @param uidState UID state of the app noteOp/startOp was called for
|
||||
* @param flags OpFlags of the call
|
||||
*/
|
||||
public void accessed(int proxyUid, @Nullable String proxyPackageName,
|
||||
@Nullable String proxyAttributionTag, @AppOpsManager.UidState int uidState,
|
||||
@AppOpsManager.OpFlags int flags) {
|
||||
long accessTime = System.currentTimeMillis();
|
||||
accessed(accessTime, -1, proxyUid, proxyPackageName,
|
||||
proxyAttributionTag, uidState, flags);
|
||||
|
||||
mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
|
||||
parent.packageName, tag, uidState, flags, accessTime,
|
||||
AppOpsManager.ATTRIBUTION_FLAGS_NONE, AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an access that was previously collected.
|
||||
*
|
||||
* @param noteTime The time of the event
|
||||
* @param duration The duration of the event
|
||||
* @param proxyUid The uid of the proxy
|
||||
* @param proxyPackageName The package name of the proxy
|
||||
* @param proxyAttributionTag the attributionTag in the proxies package
|
||||
* @param uidState UID state of the app noteOp/startOp was called for
|
||||
* @param flags OpFlags of the call
|
||||
*/
|
||||
@SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService
|
||||
public void accessed(long noteTime, long duration, int proxyUid,
|
||||
@Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
|
||||
@AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags) {
|
||||
long key = makeKey(uidState, flags);
|
||||
|
||||
if (mAccessEvents == null) {
|
||||
mAccessEvents = new LongSparseArray<>(1);
|
||||
}
|
||||
|
||||
AppOpsManager.OpEventProxyInfo proxyInfo = null;
|
||||
if (proxyUid != Process.INVALID_UID) {
|
||||
proxyInfo = mAppOpsService.mOpEventProxyInfoPool.acquire(proxyUid, proxyPackageName,
|
||||
proxyAttributionTag);
|
||||
}
|
||||
|
||||
AppOpsManager.NoteOpEvent existingEvent = mAccessEvents.get(key);
|
||||
if (existingEvent != null) {
|
||||
existingEvent.reinit(noteTime, duration, proxyInfo,
|
||||
mAppOpsService.mOpEventProxyInfoPool);
|
||||
} else {
|
||||
mAccessEvents.put(key, new AppOpsManager.NoteOpEvent(noteTime, duration, proxyInfo));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update state when noteOp/startOp was rejected.
|
||||
*
|
||||
* @param uidState UID state of the app noteOp is called for
|
||||
* @param flags OpFlags of the call
|
||||
*/
|
||||
public void rejected(@AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags) {
|
||||
rejected(System.currentTimeMillis(), uidState, flags);
|
||||
|
||||
mAppOpsService.mHistoricalRegistry.incrementOpRejected(parent.op, parent.uid,
|
||||
parent.packageName, tag, uidState, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an rejection that was previously collected
|
||||
*
|
||||
* @param noteTime The time of the event
|
||||
* @param uidState UID state of the app noteOp/startOp was called for
|
||||
* @param flags OpFlags of the call
|
||||
*/
|
||||
@SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService
|
||||
public void rejected(long noteTime, @AppOpsManager.UidState int uidState,
|
||||
@AppOpsManager.OpFlags int flags) {
|
||||
long key = makeKey(uidState, flags);
|
||||
|
||||
if (mRejectEvents == null) {
|
||||
mRejectEvents = new LongSparseArray<>(1);
|
||||
}
|
||||
|
||||
// We do not collect proxy information for rejections yet
|
||||
AppOpsManager.NoteOpEvent existingEvent = mRejectEvents.get(key);
|
||||
if (existingEvent != null) {
|
||||
existingEvent.reinit(noteTime, -1, null, mAppOpsService.mOpEventProxyInfoPool);
|
||||
} else {
|
||||
mRejectEvents.put(key, new AppOpsManager.NoteOpEvent(noteTime, -1, null));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update state when start was called
|
||||
*
|
||||
* @param clientId Id of the startOp caller
|
||||
* @param proxyUid The UID of the proxy app
|
||||
* @param proxyPackageName The package name of the proxy app
|
||||
* @param proxyAttributionTag The attribution tag of the proxy app
|
||||
* @param uidState UID state of the app startOp is called for
|
||||
* @param flags The proxy flags
|
||||
* @param attributionFlags The attribution flags associated with this operation.
|
||||
* @param attributionChainId The if of the attribution chain this operations is a part of.
|
||||
*/
|
||||
public void started(@NonNull IBinder clientId, int proxyUid,
|
||||
@Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
|
||||
@AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags,
|
||||
@AppOpsManager.AttributionFlags
|
||||
int attributionFlags, int attributionChainId) throws RemoteException {
|
||||
started(clientId, proxyUid, proxyPackageName, proxyAttributionTag,
|
||||
uidState, flags, /*triggerCallbackIfNeeded*/ true, attributionFlags,
|
||||
attributionChainId);
|
||||
}
|
||||
|
||||
private void started(@NonNull IBinder clientId, int proxyUid,
|
||||
@Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
|
||||
@AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags,
|
||||
boolean triggerCallbackIfNeeded, @AppOpsManager.AttributionFlags int attributionFlags,
|
||||
int attributionChainId) throws RemoteException {
|
||||
startedOrPaused(clientId, proxyUid, proxyPackageName,
|
||||
proxyAttributionTag, uidState, flags, triggerCallbackIfNeeded,
|
||||
/*triggerCallbackIfNeeded*/ true, attributionFlags, attributionChainId);
|
||||
}
|
||||
|
||||
@SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService
|
||||
private void startedOrPaused(@NonNull IBinder clientId, int proxyUid,
|
||||
@Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
|
||||
@AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags,
|
||||
boolean triggerCallbackIfNeeded, boolean isStarted, @AppOpsManager.AttributionFlags
|
||||
int attributionFlags, int attributionChainId) throws RemoteException {
|
||||
if (triggerCallbackIfNeeded && !parent.isRunning() && isStarted) {
|
||||
mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
|
||||
parent.packageName, tag, true, attributionFlags, attributionChainId);
|
||||
}
|
||||
|
||||
if (isStarted && mInProgressEvents == null) {
|
||||
mInProgressEvents = new ArrayMap<>(1);
|
||||
} else if (!isStarted && mPausedInProgressEvents == null) {
|
||||
mPausedInProgressEvents = new ArrayMap<>(1);
|
||||
}
|
||||
ArrayMap<IBinder, AppOpsService.InProgressStartOpEvent> events = isStarted
|
||||
? mInProgressEvents : mPausedInProgressEvents;
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
AppOpsService.InProgressStartOpEvent event = events.get(clientId);
|
||||
if (event == null) {
|
||||
event = mAppOpsService.mInProgressStartOpEventPool.acquire(startTime,
|
||||
SystemClock.elapsedRealtime(), clientId, tag,
|
||||
PooledLambda.obtainRunnable(AppOpsService::onClientDeath, this, clientId),
|
||||
proxyUid, proxyPackageName, proxyAttributionTag, uidState, flags,
|
||||
attributionFlags, attributionChainId);
|
||||
events.put(clientId, event);
|
||||
} else {
|
||||
if (uidState != event.getUidState()) {
|
||||
onUidStateChanged(uidState);
|
||||
}
|
||||
}
|
||||
|
||||
event.numUnfinishedStarts++;
|
||||
|
||||
if (isStarted) {
|
||||
mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
|
||||
parent.packageName, tag, uidState, flags, startTime, attributionFlags,
|
||||
attributionChainId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update state when finishOp was called. Will finish started ops, and delete paused ops.
|
||||
*
|
||||
* @param clientId Id of the finishOp caller
|
||||
*/
|
||||
public void finished(@NonNull IBinder clientId) {
|
||||
finished(clientId, true);
|
||||
}
|
||||
|
||||
private void finished(@NonNull IBinder clientId, boolean triggerCallbackIfNeeded) {
|
||||
finishOrPause(clientId, triggerCallbackIfNeeded, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update state when paused or finished is called. If pausing, it records the op as
|
||||
* stopping in the HistoricalRegistry, but does not delete it.
|
||||
*/
|
||||
@SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService
|
||||
private void finishOrPause(@NonNull IBinder clientId, boolean triggerCallbackIfNeeded,
|
||||
boolean isPausing) {
|
||||
int indexOfToken = isRunning() ? mInProgressEvents.indexOfKey(clientId) : -1;
|
||||
if (indexOfToken < 0) {
|
||||
finishPossiblyPaused(clientId, isPausing);
|
||||
return;
|
||||
}
|
||||
|
||||
AppOpsService.InProgressStartOpEvent event = mInProgressEvents.valueAt(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);
|
||||
}
|
||||
|
||||
AppOpsManager.OpEventProxyInfo proxyCopy = event.getProxy() != null
|
||||
? new AppOpsManager.OpEventProxyInfo(event.getProxy()) : null;
|
||||
|
||||
long accessDurationMillis =
|
||||
SystemClock.elapsedRealtime() - event.getStartElapsedTime();
|
||||
AppOpsManager.NoteOpEvent finishedEvent = new AppOpsManager.NoteOpEvent(
|
||||
event.getStartTime(),
|
||||
accessDurationMillis, proxyCopy);
|
||||
mAccessEvents.put(makeKey(event.getUidState(), event.getFlags()),
|
||||
finishedEvent);
|
||||
|
||||
mAppOpsService.mHistoricalRegistry.increaseOpAccessDuration(parent.op, parent.uid,
|
||||
parent.packageName, tag, event.getUidState(),
|
||||
event.getFlags(), finishedEvent.getNoteTime(), finishedEvent.getDuration(),
|
||||
event.getAttributionFlags(), event.getAttributionChainId());
|
||||
|
||||
if (!isPausing) {
|
||||
mAppOpsService.mInProgressStartOpEventPool.release(event);
|
||||
if (mInProgressEvents.isEmpty()) {
|
||||
mInProgressEvents = null;
|
||||
|
||||
// TODO ntmyren: Also callback for single attribution tag activity changes
|
||||
if (triggerCallbackIfNeeded && !parent.isRunning()) {
|
||||
mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op,
|
||||
parent.uid, parent.packageName, tag, false,
|
||||
event.getAttributionFlags(), event.getAttributionChainId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Finish or pause (no-op) an already paused op
|
||||
@SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService
|
||||
private void finishPossiblyPaused(@NonNull IBinder clientId, boolean isPausing) {
|
||||
if (!isPaused()) {
|
||||
Slog.wtf(AppOpsService.TAG, "No ops running or paused");
|
||||
return;
|
||||
}
|
||||
|
||||
int indexOfToken = mPausedInProgressEvents.indexOfKey(clientId);
|
||||
if (indexOfToken < 0) {
|
||||
Slog.wtf(AppOpsService.TAG, "No op running or paused for the client");
|
||||
return;
|
||||
} else if (isPausing) {
|
||||
// already paused
|
||||
return;
|
||||
}
|
||||
|
||||
// no need to record a paused event finishing.
|
||||
AppOpsService.InProgressStartOpEvent event = mPausedInProgressEvents.valueAt(indexOfToken);
|
||||
event.numUnfinishedStarts--;
|
||||
if (event.numUnfinishedStarts == 0) {
|
||||
mPausedInProgressEvents.removeAt(indexOfToken);
|
||||
mAppOpsService.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, @AppOpsManager.OpFlags int flags,
|
||||
@AppOpsManager.AttributionFlags
|
||||
int attributionFlags, int attributionChainId) throws RemoteException {
|
||||
startedOrPaused(clientId, proxyUid, proxyPackageName, proxyAttributionTag,
|
||||
uidState, flags, true, false, attributionFlags, attributionChainId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pause all currently started ops. This will create a HistoricalRegistry
|
||||
*/
|
||||
public void pause() {
|
||||
if (!isRunning()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mPausedInProgressEvents == null) {
|
||||
mPausedInProgressEvents = new ArrayMap<>(1);
|
||||
}
|
||||
|
||||
for (int i = 0; i < mInProgressEvents.size(); i++) {
|
||||
AppOpsService.InProgressStartOpEvent event = mInProgressEvents.valueAt(i);
|
||||
mPausedInProgressEvents.put(event.getClientId(), event);
|
||||
finishOrPause(event.getClientId(), true, true);
|
||||
|
||||
mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
|
||||
parent.packageName, tag, false,
|
||||
event.getAttributionFlags(), event.getAttributionChainId());
|
||||
}
|
||||
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 (!isPaused()) {
|
||||
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++) {
|
||||
AppOpsService.InProgressStartOpEvent event = mPausedInProgressEvents.valueAt(i);
|
||||
mInProgressEvents.put(event.getClientId(), event);
|
||||
event.setStartElapsedTime(SystemClock.elapsedRealtime());
|
||||
event.setStartTime(startTime);
|
||||
mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
|
||||
parent.packageName, tag, event.getUidState(), event.getFlags(), startTime,
|
||||
event.getAttributionFlags(), event.getAttributionChainId());
|
||||
if (shouldSendActive) {
|
||||
mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
|
||||
parent.packageName, tag, true, event.getAttributionFlags(),
|
||||
event.getAttributionChainId());
|
||||
}
|
||||
// Note: this always sends MODE_ALLOWED, even if the mode is FOREGROUND
|
||||
// TODO ntmyren: figure out how to get the real mode.
|
||||
mAppOpsService.scheduleOpStartedIfNeededLocked(parent.op, parent.uid,
|
||||
parent.packageName, tag, event.getFlags(), MODE_ALLOWED, START_TYPE_RESUMED,
|
||||
event.getAttributionFlags(), event.getAttributionChainId());
|
||||
}
|
||||
mPausedInProgressEvents = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called in the case the client dies without calling finish first
|
||||
*
|
||||
* @param clientId The client that died
|
||||
*/
|
||||
void onClientDeath(@NonNull IBinder clientId) {
|
||||
synchronized (mAppOpsService) {
|
||||
if (!isPaused() && !isRunning()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ArrayMap<IBinder, AppOpsService.InProgressStartOpEvent> events = isPaused()
|
||||
? mPausedInProgressEvents : mInProgressEvents;
|
||||
AppOpsService.InProgressStartOpEvent deadEvent = events.get(clientId);
|
||||
if (deadEvent != null) {
|
||||
deadEvent.numUnfinishedStarts = 1;
|
||||
}
|
||||
|
||||
finished(clientId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify that the state of the uid changed
|
||||
*
|
||||
* @param newState The new state
|
||||
*/
|
||||
public void onUidStateChanged(@AppOpsManager.UidState int newState) {
|
||||
if (!isPaused() && !isRunning()) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean isRunning = isRunning();
|
||||
ArrayMap<IBinder, AppOpsService.InProgressStartOpEvent> events =
|
||||
isRunning ? mInProgressEvents : mPausedInProgressEvents;
|
||||
|
||||
int numInProgressEvents = events.size();
|
||||
List<IBinder> binders = new ArrayList<>(events.keySet());
|
||||
for (int i = 0; i < numInProgressEvents; i++) {
|
||||
AppOpsService.InProgressStartOpEvent event = events.get(binders.get(i));
|
||||
|
||||
if (event != null && event.getUidState() != newState) {
|
||||
try {
|
||||
// Remove all but one unfinished start count and then call finished() to
|
||||
// remove start event object
|
||||
int numPreviousUnfinishedStarts = event.numUnfinishedStarts;
|
||||
event.numUnfinishedStarts = 1;
|
||||
AppOpsManager.OpEventProxyInfo proxy = event.getProxy();
|
||||
|
||||
finished(event.getClientId(), false);
|
||||
|
||||
// Call started() to add a new start event object and then add the
|
||||
// previously removed unfinished start counts back
|
||||
if (proxy != null) {
|
||||
startedOrPaused(event.getClientId(), proxy.getUid(),
|
||||
proxy.getPackageName(), proxy.getAttributionTag(), newState,
|
||||
event.getFlags(), false, isRunning,
|
||||
event.getAttributionFlags(), event.getAttributionChainId());
|
||||
} else {
|
||||
startedOrPaused(event.getClientId(), Process.INVALID_UID, null, null,
|
||||
newState, event.getFlags(), false, isRunning,
|
||||
event.getAttributionFlags(), event.getAttributionChainId());
|
||||
}
|
||||
|
||||
events = isRunning ? mInProgressEvents : mPausedInProgressEvents;
|
||||
AppOpsService.InProgressStartOpEvent newEvent = events.get(binders.get(i));
|
||||
if (newEvent != null) {
|
||||
newEvent.numUnfinishedStarts += numPreviousUnfinishedStarts - 1;
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
if (AppOpsService.DEBUG) {
|
||||
Slog.e(AppOpsService.TAG,
|
||||
"Cannot switch to new uidState " + newState);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine {@code a} and {@code b} and return the result. The result might be {@code a}
|
||||
* or {@code b}. If there is an event for the same key in both the later event is retained.
|
||||
*/
|
||||
private @Nullable LongSparseArray<AppOpsManager.NoteOpEvent> add(
|
||||
@Nullable LongSparseArray<AppOpsManager.NoteOpEvent> a,
|
||||
@Nullable LongSparseArray<AppOpsManager.NoteOpEvent> b) {
|
||||
if (a == null) {
|
||||
return b;
|
||||
}
|
||||
|
||||
if (b == null) {
|
||||
return a;
|
||||
}
|
||||
|
||||
int numEventsToAdd = b.size();
|
||||
for (int i = 0; i < numEventsToAdd; i++) {
|
||||
long keyOfEventToAdd = b.keyAt(i);
|
||||
AppOpsManager.NoteOpEvent bEvent = b.valueAt(i);
|
||||
AppOpsManager.NoteOpEvent aEvent = a.get(keyOfEventToAdd);
|
||||
|
||||
if (aEvent == null || bEvent.getNoteTime() > aEvent.getNoteTime()) {
|
||||
a.put(keyOfEventToAdd, bEvent);
|
||||
}
|
||||
}
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all data from the {@code opToAdd} to this op.
|
||||
*
|
||||
* <p>If there is an event for the same key in both the later event is retained.
|
||||
* <p>{@code opToAdd} should not be used after this method is called.
|
||||
*
|
||||
* @param opToAdd The op to add
|
||||
*/
|
||||
@SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService
|
||||
public void add(@NonNull AttributedOp opToAdd) {
|
||||
if (opToAdd.isRunning() || opToAdd.isPaused()) {
|
||||
ArrayMap<IBinder, AppOpsService.InProgressStartOpEvent> ignoredEvents =
|
||||
opToAdd.isRunning()
|
||||
? opToAdd.mInProgressEvents : opToAdd.mPausedInProgressEvents;
|
||||
Slog.w(AppOpsService.TAG, "Ignoring " + ignoredEvents.size() + " app-ops, running: "
|
||||
+ opToAdd.isRunning());
|
||||
|
||||
int numInProgressEvents = ignoredEvents.size();
|
||||
for (int i = 0; i < numInProgressEvents; i++) {
|
||||
AppOpsService.InProgressStartOpEvent event = ignoredEvents.valueAt(i);
|
||||
|
||||
event.finish();
|
||||
mAppOpsService.mInProgressStartOpEventPool.release(event);
|
||||
}
|
||||
}
|
||||
|
||||
mAccessEvents = add(mAccessEvents, opToAdd.mAccessEvents);
|
||||
mRejectEvents = add(mRejectEvents, opToAdd.mRejectEvents);
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
return mInProgressEvents != null && !mInProgressEvents.isEmpty();
|
||||
}
|
||||
|
||||
public boolean isPaused() {
|
||||
return mPausedInProgressEvents != null && !mPausedInProgressEvents.isEmpty();
|
||||
}
|
||||
|
||||
boolean hasAnyTime() {
|
||||
return (mAccessEvents != null && mAccessEvents.size() > 0)
|
||||
|| (mRejectEvents != null && mRejectEvents.size() > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone a {@link LongSparseArray} and clone all values.
|
||||
*/
|
||||
private @Nullable LongSparseArray<AppOpsManager.NoteOpEvent> deepClone(
|
||||
@Nullable LongSparseArray<AppOpsManager.NoteOpEvent> original) {
|
||||
if (original == null) {
|
||||
return original;
|
||||
}
|
||||
|
||||
int size = original.size();
|
||||
LongSparseArray<AppOpsManager.NoteOpEvent> clone = new LongSparseArray<>(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
clone.put(original.keyAt(i), new AppOpsManager.NoteOpEvent(original.valueAt(i)));
|
||||
}
|
||||
|
||||
return clone;
|
||||
}
|
||||
|
||||
@NonNull AppOpsManager.AttributedOpEntry createAttributedOpEntryLocked() {
|
||||
LongSparseArray<AppOpsManager.NoteOpEvent> accessEvents = deepClone(mAccessEvents);
|
||||
|
||||
// Add in progress events as access events
|
||||
if (isRunning()) {
|
||||
long now = SystemClock.elapsedRealtime();
|
||||
int numInProgressEvents = mInProgressEvents.size();
|
||||
|
||||
if (accessEvents == null) {
|
||||
accessEvents = new LongSparseArray<>(numInProgressEvents);
|
||||
}
|
||||
|
||||
for (int i = 0; i < numInProgressEvents; i++) {
|
||||
AppOpsService.InProgressStartOpEvent event = mInProgressEvents.valueAt(i);
|
||||
|
||||
accessEvents.append(makeKey(event.getUidState(), event.getFlags()),
|
||||
new AppOpsManager.NoteOpEvent(event.getStartTime(),
|
||||
now - event.getStartElapsedTime(),
|
||||
event.getProxy()));
|
||||
}
|
||||
}
|
||||
|
||||
LongSparseArray<AppOpsManager.NoteOpEvent> rejectEvents = deepClone(mRejectEvents);
|
||||
|
||||
return new AppOpsManager.AttributedOpEntry(parent.op, isRunning(), accessEvents,
|
||||
rejectEvents);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user