Merge changes from topic "extract-attributedop"
* changes: Move related classes into AttributedOp Refactor: move AttributedOp out of AppOpsService Prepare to move AttributedOp out of AppOpsService
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;
|
||||
@@ -71,7 +68,6 @@ import static android.content.pm.PermissionInfo.PROTECTION_FLAG_APPOP;
|
||||
import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.IntRange;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.UserIdInt;
|
||||
@@ -131,10 +127,7 @@ 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;
|
||||
import android.util.Slog;
|
||||
import android.util.SparseArray;
|
||||
import android.util.SparseBooleanArray;
|
||||
@@ -198,7 +191,6 @@ import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Objects;
|
||||
import java.util.Scanner;
|
||||
import java.util.Set;
|
||||
@@ -241,14 +233,22 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch
|
||||
private final @Nullable File mNoteOpCallerStacktracesFile;
|
||||
final Handler mHandler;
|
||||
|
||||
/** Pool for {@link OpEventProxyInfoPool} to avoid to constantly reallocate new objects */
|
||||
/**
|
||||
* Pool for {@link AttributedOp.OpEventProxyInfoPool} to avoid to constantly reallocate new
|
||||
* objects
|
||||
*/
|
||||
@GuardedBy("this")
|
||||
private final OpEventProxyInfoPool mOpEventProxyInfoPool = new OpEventProxyInfoPool();
|
||||
final AttributedOp.OpEventProxyInfoPool mOpEventProxyInfoPool =
|
||||
new AttributedOp.OpEventProxyInfoPool(MAX_UNUSED_POOLED_OBJECTS);
|
||||
|
||||
/** Pool for {@link InProgressStartOpEventPool} to avoid to constantly reallocate new objects */
|
||||
/**
|
||||
* Pool for {@link AttributedOp.InProgressStartOpEventPool} to avoid to constantly reallocate
|
||||
* new objects
|
||||
*/
|
||||
@GuardedBy("this")
|
||||
private final InProgressStartOpEventPool mInProgressStartOpEventPool =
|
||||
new InProgressStartOpEventPool();
|
||||
final AttributedOp.InProgressStartOpEventPool mInProgressStartOpEventPool =
|
||||
new AttributedOp.InProgressStartOpEventPool(mOpEventProxyInfoPool,
|
||||
MAX_UNUSED_POOLED_OBJECTS);
|
||||
|
||||
private final AppOpsManagerInternalImpl mAppOpsManagerInternal
|
||||
= new AppOpsManagerInternalImpl();
|
||||
@@ -382,60 +382,6 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch
|
||||
return mUidStateTracker;
|
||||
}
|
||||
|
||||
/**
|
||||
* An unsynchronized pool of {@link OpEventProxyInfo} objects.
|
||||
*/
|
||||
private class OpEventProxyInfoPool extends SimplePool<OpEventProxyInfo> {
|
||||
OpEventProxyInfoPool() {
|
||||
super(MAX_UNUSED_POOLED_OBJECTS);
|
||||
}
|
||||
|
||||
OpEventProxyInfo acquire(@IntRange(from = 0) int uid, @Nullable String packageName,
|
||||
@Nullable String attributionTag) {
|
||||
OpEventProxyInfo recycled = acquire();
|
||||
if (recycled != null) {
|
||||
recycled.reinit(uid, packageName, attributionTag);
|
||||
return recycled;
|
||||
}
|
||||
|
||||
return new OpEventProxyInfo(uid, packageName, attributionTag);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An unsynchronized pool of {@link InProgressStartOpEvent} objects.
|
||||
*/
|
||||
private class InProgressStartOpEventPool extends SimplePool<InProgressStartOpEvent> {
|
||||
InProgressStartOpEventPool() {
|
||||
super(MAX_UNUSED_POOLED_OBJECTS);
|
||||
}
|
||||
|
||||
InProgressStartOpEvent acquire(long startTime, long elapsedTime, @NonNull IBinder clientId,
|
||||
@Nullable String attributionTag, @NonNull Runnable onDeath, int proxyUid,
|
||||
@Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
|
||||
@AppOpsManager.UidState int uidState, @OpFlags int flags, @AttributionFlags
|
||||
int attributionFlags, int attributionChainId) throws RemoteException {
|
||||
|
||||
InProgressStartOpEvent recycled = acquire();
|
||||
|
||||
OpEventProxyInfo proxyInfo = null;
|
||||
if (proxyUid != Process.INVALID_UID) {
|
||||
proxyInfo = mOpEventProxyInfoPool.acquire(proxyUid, proxyPackageName,
|
||||
proxyAttributionTag);
|
||||
}
|
||||
|
||||
if (recycled != null) {
|
||||
recycled.reinit(startTime, elapsedTime, clientId, attributionTag, onDeath,
|
||||
uidState, flags, proxyInfo, attributionFlags, attributionChainId,
|
||||
mOpEventProxyInfoPool);
|
||||
return recycled;
|
||||
}
|
||||
|
||||
return new InProgressStartOpEvent(startTime, elapsedTime, clientId, attributionTag,
|
||||
onDeath, uidState, proxyInfo, flags, attributionFlags, attributionChainId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* All times are in milliseconds. These constants are kept synchronized with the system
|
||||
* global Settings. Any access to this class or its fields should be done while
|
||||
@@ -652,740 +598,6 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch
|
||||
}
|
||||
}
|
||||
|
||||
/** A in progress startOp->finishOp event */
|
||||
private static final class InProgressStartOpEvent implements IBinder.DeathRecipient {
|
||||
/** Wall clock time of startOp event (not monotonic) */
|
||||
private long mStartTime;
|
||||
|
||||
/** Elapsed time since boot of startOp event */
|
||||
private long mStartElapsedTime;
|
||||
|
||||
/** Id of the client that started the event */
|
||||
private @NonNull IBinder mClientId;
|
||||
|
||||
/** The attribution tag for this operation */
|
||||
private @Nullable String mAttributionTag;
|
||||
|
||||
/** To call when client dies */
|
||||
private @NonNull Runnable mOnDeath;
|
||||
|
||||
/** uidstate used when calling startOp */
|
||||
private @AppOpsManager.UidState int mUidState;
|
||||
|
||||
/** Proxy information of the startOp event */
|
||||
private @Nullable OpEventProxyInfo mProxy;
|
||||
|
||||
/** Proxy flag information */
|
||||
private @OpFlags int mFlags;
|
||||
|
||||
/** How many times the op was started but not finished yet */
|
||||
int numUnfinishedStarts;
|
||||
|
||||
/** The attribution flags related to this event */
|
||||
private @AttributionFlags int mAttributionFlags;
|
||||
|
||||
/** The id of the attribution chain this even is a part of */
|
||||
private int mAttributionChainId;
|
||||
|
||||
/**
|
||||
* Create a new {@link InProgressStartOpEvent}.
|
||||
*
|
||||
* @param startTime The time {@link #startOperation} was called
|
||||
* @param startElapsedTime The elapsed time when {@link #startOperation} was called
|
||||
* @param clientId The client id of the caller of {@link #startOperation}
|
||||
* @param attributionTag The attribution tag for the operation.
|
||||
* @param onDeath The code to execute on client death
|
||||
* @param uidState The uidstate of the app {@link #startOperation} was called for
|
||||
* @param attributionFlags the attribution flags for this operation.
|
||||
* @param attributionChainId the unique id of the attribution chain this op is a part of.
|
||||
* @param proxy The proxy information, if {@link #startProxyOperation} was called
|
||||
* @param flags The trusted/nontrusted/self flags.
|
||||
*
|
||||
* @throws RemoteException If the client is dying
|
||||
*/
|
||||
private InProgressStartOpEvent(long startTime, long startElapsedTime,
|
||||
@NonNull IBinder clientId, @Nullable String attributionTag,
|
||||
@NonNull Runnable onDeath, @AppOpsManager.UidState int uidState,
|
||||
@Nullable OpEventProxyInfo proxy, @OpFlags int flags,
|
||||
@AttributionFlags int attributionFlags, int attributionChainId)
|
||||
throws RemoteException {
|
||||
mStartTime = startTime;
|
||||
mStartElapsedTime = startElapsedTime;
|
||||
mClientId = clientId;
|
||||
mAttributionTag = attributionTag;
|
||||
mOnDeath = onDeath;
|
||||
mUidState = uidState;
|
||||
mProxy = proxy;
|
||||
mFlags = flags;
|
||||
mAttributionFlags = attributionFlags;
|
||||
mAttributionChainId = attributionChainId;
|
||||
|
||||
clientId.linkToDeath(this, 0);
|
||||
}
|
||||
|
||||
/** Clean up event */
|
||||
public void finish() {
|
||||
try {
|
||||
mClientId.unlinkToDeath(this, 0);
|
||||
} catch (NoSuchElementException e) {
|
||||
// Either not linked, or already unlinked. Either way, nothing to do.
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void binderDied() {
|
||||
mOnDeath.run();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reinit existing object with new state.
|
||||
*
|
||||
* @param startTime The time {@link #startOperation} was called
|
||||
* @param startElapsedTime The elapsed time when {@link #startOperation} was called
|
||||
* @param clientId The client id of the caller of {@link #startOperation}
|
||||
* @param attributionTag The attribution tag for this operation.
|
||||
* @param onDeath The code to execute on client death
|
||||
* @param uidState The uidstate of the app {@link #startOperation} was called for
|
||||
* @param flags The flags relating to the proxy
|
||||
* @param proxy The proxy information, if {@link #startProxyOperation} was called
|
||||
* @param attributionFlags the attribution flags for this operation.
|
||||
* @param attributionChainId the unique id of the attribution chain this op is a part of.
|
||||
* @param proxyPool The pool to release previous {@link OpEventProxyInfo} to
|
||||
*
|
||||
* @throws RemoteException If the client is dying
|
||||
*/
|
||||
public void reinit(long startTime, long startElapsedTime, @NonNull IBinder clientId,
|
||||
@Nullable String attributionTag, @NonNull Runnable onDeath,
|
||||
@AppOpsManager.UidState int uidState, @OpFlags int flags,
|
||||
@Nullable OpEventProxyInfo proxy, @AttributionFlags int attributionFlags,
|
||||
int attributionChainId, @NonNull Pools.Pool<OpEventProxyInfo> proxyPool
|
||||
) throws RemoteException {
|
||||
mStartTime = startTime;
|
||||
mStartElapsedTime = startElapsedTime;
|
||||
mClientId = clientId;
|
||||
mAttributionTag = attributionTag;
|
||||
mOnDeath = onDeath;
|
||||
mUidState = uidState;
|
||||
mFlags = flags;
|
||||
|
||||
if (mProxy != null) {
|
||||
proxyPool.release(mProxy);
|
||||
}
|
||||
mProxy = proxy;
|
||||
mAttributionFlags = attributionFlags;
|
||||
mAttributionChainId = attributionChainId;
|
||||
|
||||
clientId.linkToDeath(this, 0);
|
||||
}
|
||||
|
||||
/** @return Wall clock time of startOp event */
|
||||
public long getStartTime() {
|
||||
return mStartTime;
|
||||
}
|
||||
|
||||
/** @return Elapsed time since boot of startOp event */
|
||||
public long getStartElapsedTime() {
|
||||
return mStartElapsedTime;
|
||||
}
|
||||
|
||||
/** @return Id of the client that started the event */
|
||||
public @NonNull IBinder getClientId() {
|
||||
return mClientId;
|
||||
}
|
||||
|
||||
/** @return uidstate used when calling startOp */
|
||||
public @AppOpsManager.UidState int getUidState() {
|
||||
return mUidState;
|
||||
}
|
||||
|
||||
/** @return proxy tag for the access */
|
||||
public @Nullable OpEventProxyInfo getProxy() {
|
||||
return mProxy;
|
||||
}
|
||||
|
||||
/** @return flags used for the access */
|
||||
public @OpFlags int getFlags() {
|
||||
return mFlags;
|
||||
}
|
||||
|
||||
/** @return attributoin flags used for the access */
|
||||
public @AttributionFlags int getAttributionFlags() {
|
||||
return mAttributionFlags;
|
||||
}
|
||||
|
||||
/** @return attribution chain id for the access */
|
||||
public int getAttributionChainId() {
|
||||
return mAttributionChainId;
|
||||
}
|
||||
}
|
||||
|
||||
private final class AttributedOp {
|
||||
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")
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
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 = mOpEventProxyInfoPool.acquire(proxyUid, proxyPackageName,
|
||||
proxyAttributionTag);
|
||||
}
|
||||
|
||||
NoteOpEvent existingEvent = mAccessEvents.get(key);
|
||||
if (existingEvent != null) {
|
||||
existingEvent.reinit(noteTime, duration, proxyInfo, 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);
|
||||
|
||||
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, 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) {
|
||||
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 = 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.mUidState) {
|
||||
onUidStateChanged(uidState);
|
||||
}
|
||||
}
|
||||
|
||||
event.numUnfinishedStarts++;
|
||||
|
||||
if (isStarted) {
|
||||
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);
|
||||
|
||||
mHistoricalRegistry.increaseOpAccessDuration(parent.op, parent.uid,
|
||||
parent.packageName, tag, event.getUidState(),
|
||||
event.getFlags(), finishedEvent.getNoteTime(), finishedEvent.getDuration(),
|
||||
event.getAttributionFlags(), event.getAttributionChainId());
|
||||
|
||||
if (!isPausing) {
|
||||
mInProgressStartOpEventPool.release(event);
|
||||
if (mInProgressEvents.isEmpty()) {
|
||||
mInProgressEvents = null;
|
||||
|
||||
// TODO ntmyren: Also callback for single attribution tag activity changes
|
||||
if (triggerCallbackIfNeeded && !parent.isRunning()) {
|
||||
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);
|
||||
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.mClientId, event);
|
||||
finishOrPause(event.mClientId, true, true);
|
||||
|
||||
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.mClientId, event);
|
||||
event.mStartElapsedTime = SystemClock.elapsedRealtime();
|
||||
event.mStartTime = startTime;
|
||||
mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
|
||||
parent.packageName, tag, event.mUidState, event.mFlags, startTime,
|
||||
event.getAttributionFlags(), event.getAttributionChainId());
|
||||
if (shouldSendActive) {
|
||||
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.
|
||||
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 (AppOpsService.this) {
|
||||
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();
|
||||
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;
|
||||
@@ -1425,7 +637,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch
|
||||
|
||||
attributedOp = mAttributions.get(attributionTag);
|
||||
if (attributedOp == null) {
|
||||
attributedOp = new AttributedOp(attributionTag, parent);
|
||||
attributedOp = new AttributedOp(AppOpsService.this, attributionTag, parent);
|
||||
mAttributions.put(attributionTag, attributedOp);
|
||||
}
|
||||
|
||||
@@ -1675,7 +887,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch
|
||||
/**
|
||||
* Call {@link AttributedOp#onClientDeath attributedOp.onClientDeath(clientId)}.
|
||||
*/
|
||||
private static void onClientDeath(@NonNull AttributedOp attributedOp,
|
||||
static void onClientDeath(@NonNull AttributedOp attributedOp,
|
||||
@NonNull IBinder clientId) {
|
||||
attributedOp.onClientDeath(clientId);
|
||||
}
|
||||
@@ -1916,7 +1128,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch
|
||||
PackageInfo pi = getPackageManagerInternal().getPackageInfo(packageName,
|
||||
PackageManager.GET_PERMISSIONS, Process.myUid(), mContext.getUserId());
|
||||
if (isSamplingTarget(pi)) {
|
||||
synchronized (this) {
|
||||
synchronized (AppOpsService.this) {
|
||||
mRarelyUsedPackages.add(packageName);
|
||||
}
|
||||
}
|
||||
@@ -4016,7 +3228,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduleOpActiveChangedIfNeededLocked(int code, int uid, @NonNull
|
||||
void scheduleOpActiveChangedIfNeededLocked(int code, int uid, @NonNull
|
||||
String packageName, @Nullable String attributionTag, boolean active, @AttributionFlags
|
||||
int attributionFlags, int attributionChainId) {
|
||||
ArraySet<ActiveCallback> dispatchedCallbacks = null;
|
||||
@@ -4069,7 +3281,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduleOpStartedIfNeededLocked(int code, int uid, String pkgName,
|
||||
void scheduleOpStartedIfNeededLocked(int code, int uid, String pkgName,
|
||||
String attributionTag, @OpFlags int flags, @Mode int result,
|
||||
@AppOpsManager.OnOpStartedListener.StartedType int startedType,
|
||||
@AttributionFlags int attributionFlags, int attributionChainId) {
|
||||
@@ -5700,10 +4912,11 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch
|
||||
long maxNumStarts = 0;
|
||||
int numInProgressEvents = attributedOp.mInProgressEvents.size();
|
||||
for (int i = 0; i < numInProgressEvents; i++) {
|
||||
InProgressStartOpEvent event = attributedOp.mInProgressEvents.valueAt(i);
|
||||
AttributedOp.InProgressStartOpEvent event =
|
||||
attributedOp.mInProgressEvents.valueAt(i);
|
||||
|
||||
earliestElapsedTime = Math.min(earliestElapsedTime, event.getStartElapsedTime());
|
||||
maxNumStarts = Math.max(maxNumStarts, event.numUnfinishedStarts);
|
||||
maxNumStarts = Math.max(maxNumStarts, event.mNumUnfinishedStarts);
|
||||
}
|
||||
|
||||
pw.print(prefix + "Running start at: ");
|
||||
|
||||
870
services/core/java/com/android/server/appop/AttributedOp.java
Normal file
870
services/core/java/com/android/server/appop/AttributedOp.java
Normal file
@@ -0,0 +1,870 @@
|
||||
/*
|
||||
* 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.IntRange;
|
||||
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.Pools;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.internal.util.function.pooled.PooledLambda;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
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}
|
||||
*/
|
||||
// TODO(b/248108338)
|
||||
// @GuardedBy("mAppOpsService")
|
||||
private @Nullable LongSparseArray<AppOpsManager.NoteOpEvent> mAccessEvents;
|
||||
|
||||
/**
|
||||
* Last rejected accesses for each uidState/opFlag combination
|
||||
*
|
||||
* <p>Key is {@link AppOpsManager#makeKey}
|
||||
*/
|
||||
// TODO(b/248108338)
|
||||
// @GuardedBy("mAppOpsService")
|
||||
private @Nullable LongSparseArray<AppOpsManager.NoteOpEvent> mRejectEvents;
|
||||
|
||||
/**
|
||||
* Currently in progress startOp events
|
||||
*
|
||||
* <p>Key is clientId
|
||||
*/
|
||||
// TODO(b/248108338)
|
||||
// @GuardedBy("mAppOpsService")
|
||||
@Nullable ArrayMap<IBinder, InProgressStartOpEvent> mInProgressEvents;
|
||||
|
||||
/**
|
||||
* Currently paused startOp events
|
||||
*
|
||||
* <p>Key is clientId
|
||||
*/
|
||||
// TODO(b/248108338)
|
||||
// @GuardedBy("mAppOpsService")
|
||||
@Nullable ArrayMap<IBinder, 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, 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.mNumUnfinishedStarts++;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
InProgressStartOpEvent event = mInProgressEvents.valueAt(indexOfToken);
|
||||
if (!isPausing) {
|
||||
event.mNumUnfinishedStarts--;
|
||||
}
|
||||
// If we are pausing, create a NoteOpEvent, but don't change the InProgress event
|
||||
if (event.mNumUnfinishedStarts == 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.
|
||||
InProgressStartOpEvent event = mPausedInProgressEvents.valueAt(indexOfToken);
|
||||
event.mNumUnfinishedStarts--;
|
||||
if (event.mNumUnfinishedStarts == 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++) {
|
||||
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.mNumUnfinishedStarts = 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, 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.mNumUnfinishedStarts;
|
||||
event.mNumUnfinishedStarts = 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;
|
||||
InProgressStartOpEvent newEvent = events.get(binders.get(i));
|
||||
if (newEvent != null) {
|
||||
newEvent.mNumUnfinishedStarts += 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, 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++) {
|
||||
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++) {
|
||||
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);
|
||||
}
|
||||
|
||||
/** A in progress startOp->finishOp event */
|
||||
static final class InProgressStartOpEvent implements IBinder.DeathRecipient {
|
||||
/** Wall clock time of startOp event (not monotonic) */
|
||||
private long mStartTime;
|
||||
|
||||
/** Elapsed time since boot of startOp event */
|
||||
private long mStartElapsedTime;
|
||||
|
||||
/** Id of the client that started the event */
|
||||
private @NonNull IBinder mClientId;
|
||||
|
||||
/** The attribution tag for this operation */
|
||||
private @Nullable String mAttributionTag;
|
||||
|
||||
/** To call when client dies */
|
||||
private @NonNull Runnable mOnDeath;
|
||||
|
||||
/** uidstate used when calling startOp */
|
||||
private @AppOpsManager.UidState int mUidState;
|
||||
|
||||
/** Proxy information of the startOp event */
|
||||
private @Nullable AppOpsManager.OpEventProxyInfo mProxy;
|
||||
|
||||
/** Proxy flag information */
|
||||
private @AppOpsManager.OpFlags int mFlags;
|
||||
|
||||
/** How many times the op was started but not finished yet */
|
||||
int mNumUnfinishedStarts;
|
||||
|
||||
/** The attribution flags related to this event */
|
||||
private @AppOpsManager.AttributionFlags int mAttributionFlags;
|
||||
|
||||
/** The id of the attribution chain this even is a part of */
|
||||
private int mAttributionChainId;
|
||||
|
||||
/**
|
||||
* Create a new {@link InProgressStartOpEvent}.
|
||||
*
|
||||
* @param startTime The time {@link #startOperation} was called
|
||||
* @param startElapsedTime The elapsed time when {@link #startOperation} was called
|
||||
* @param clientId The client id of the caller of {@link #startOperation}
|
||||
* @param attributionTag The attribution tag for the operation.
|
||||
* @param onDeath The code to execute on client death
|
||||
* @param uidState The uidstate of the app {@link #startOperation} was called for
|
||||
* @param attributionFlags the attribution flags for this operation.
|
||||
* @param attributionChainId the unique id of the attribution chain this op is a part of.
|
||||
* @param proxy The proxy information, if {@link #startProxyOperation} was
|
||||
* called
|
||||
* @param flags The trusted/nontrusted/self flags.
|
||||
* @throws RemoteException If the client is dying
|
||||
*/
|
||||
InProgressStartOpEvent(long startTime, long startElapsedTime,
|
||||
@NonNull IBinder clientId, @Nullable String attributionTag,
|
||||
@NonNull Runnable onDeath, @AppOpsManager.UidState int uidState,
|
||||
@Nullable AppOpsManager.OpEventProxyInfo proxy, @AppOpsManager.OpFlags int flags,
|
||||
@AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId)
|
||||
throws RemoteException {
|
||||
mStartTime = startTime;
|
||||
mStartElapsedTime = startElapsedTime;
|
||||
mClientId = clientId;
|
||||
mAttributionTag = attributionTag;
|
||||
mOnDeath = onDeath;
|
||||
mUidState = uidState;
|
||||
mProxy = proxy;
|
||||
mFlags = flags;
|
||||
mAttributionFlags = attributionFlags;
|
||||
mAttributionChainId = attributionChainId;
|
||||
|
||||
clientId.linkToDeath(this, 0);
|
||||
}
|
||||
|
||||
/** Clean up event */
|
||||
public void finish() {
|
||||
try {
|
||||
mClientId.unlinkToDeath(this, 0);
|
||||
} catch (NoSuchElementException e) {
|
||||
// Either not linked, or already unlinked. Either way, nothing to do.
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void binderDied() {
|
||||
mOnDeath.run();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reinit existing object with new state.
|
||||
*
|
||||
* @param startTime The time {@link #startOperation} was called
|
||||
* @param startElapsedTime The elapsed time when {@link #startOperation} was called
|
||||
* @param clientId The client id of the caller of {@link #startOperation}
|
||||
* @param attributionTag The attribution tag for this operation.
|
||||
* @param onDeath The code to execute on client death
|
||||
* @param uidState The uidstate of the app {@link #startOperation} was called for
|
||||
* @param flags The flags relating to the proxy
|
||||
* @param proxy The proxy information, if {@link #startProxyOperation}
|
||||
* was called
|
||||
* @param attributionFlags the attribution flags for this operation.
|
||||
* @param attributionChainId the unique id of the attribution chain this op is a part of.
|
||||
* @param proxyPool The pool to release
|
||||
* previous {@link AppOpsManager.OpEventProxyInfo} to
|
||||
* @throws RemoteException If the client is dying
|
||||
*/
|
||||
public void reinit(long startTime, long startElapsedTime, @NonNull IBinder clientId,
|
||||
@Nullable String attributionTag, @NonNull Runnable onDeath,
|
||||
@AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags,
|
||||
@Nullable AppOpsManager.OpEventProxyInfo proxy,
|
||||
@AppOpsManager.AttributionFlags int attributionFlags,
|
||||
int attributionChainId,
|
||||
@NonNull Pools.Pool<AppOpsManager.OpEventProxyInfo> proxyPool
|
||||
) throws RemoteException {
|
||||
mStartTime = startTime;
|
||||
mStartElapsedTime = startElapsedTime;
|
||||
mClientId = clientId;
|
||||
mAttributionTag = attributionTag;
|
||||
mOnDeath = onDeath;
|
||||
mUidState = uidState;
|
||||
mFlags = flags;
|
||||
|
||||
if (mProxy != null) {
|
||||
proxyPool.release(mProxy);
|
||||
}
|
||||
mProxy = proxy;
|
||||
mAttributionFlags = attributionFlags;
|
||||
mAttributionChainId = attributionChainId;
|
||||
|
||||
clientId.linkToDeath(this, 0);
|
||||
}
|
||||
|
||||
/** @return Wall clock time of startOp event */
|
||||
public long getStartTime() {
|
||||
return mStartTime;
|
||||
}
|
||||
|
||||
/** @return Elapsed time since boot of startOp event */
|
||||
public long getStartElapsedTime() {
|
||||
return mStartElapsedTime;
|
||||
}
|
||||
|
||||
/** @return Id of the client that started the event */
|
||||
public @NonNull IBinder getClientId() {
|
||||
return mClientId;
|
||||
}
|
||||
|
||||
/** @return uidstate used when calling startOp */
|
||||
public @AppOpsManager.UidState int getUidState() {
|
||||
return mUidState;
|
||||
}
|
||||
|
||||
/** @return proxy tag for the access */
|
||||
public @Nullable AppOpsManager.OpEventProxyInfo getProxy() {
|
||||
return mProxy;
|
||||
}
|
||||
|
||||
/** @return flags used for the access */
|
||||
public @AppOpsManager.OpFlags int getFlags() {
|
||||
return mFlags;
|
||||
}
|
||||
|
||||
/** @return attributoin flags used for the access */
|
||||
public @AppOpsManager.AttributionFlags int getAttributionFlags() {
|
||||
return mAttributionFlags;
|
||||
}
|
||||
|
||||
/** @return attribution chain id for the access */
|
||||
public int getAttributionChainId() {
|
||||
return mAttributionChainId;
|
||||
}
|
||||
|
||||
public void setStartTime(long startTime) {
|
||||
mStartTime = startTime;
|
||||
}
|
||||
|
||||
public void setStartElapsedTime(long startElapsedTime) {
|
||||
mStartElapsedTime = startElapsedTime;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An unsynchronized pool of {@link InProgressStartOpEvent} objects.
|
||||
*/
|
||||
static class InProgressStartOpEventPool extends Pools.SimplePool<InProgressStartOpEvent> {
|
||||
private OpEventProxyInfoPool mOpEventProxyInfoPool;
|
||||
|
||||
InProgressStartOpEventPool(OpEventProxyInfoPool opEventProxyInfoPool,
|
||||
int maxUnusedPooledObjects) {
|
||||
super(maxUnusedPooledObjects);
|
||||
this.mOpEventProxyInfoPool = opEventProxyInfoPool;
|
||||
}
|
||||
|
||||
InProgressStartOpEvent acquire(long startTime, long elapsedTime, @NonNull IBinder clientId,
|
||||
@Nullable String attributionTag, @NonNull Runnable onDeath, int proxyUid,
|
||||
@Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
|
||||
@AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags,
|
||||
@AppOpsManager.AttributionFlags
|
||||
int attributionFlags, int attributionChainId) throws RemoteException {
|
||||
|
||||
InProgressStartOpEvent recycled = acquire();
|
||||
|
||||
AppOpsManager.OpEventProxyInfo proxyInfo = null;
|
||||
if (proxyUid != Process.INVALID_UID) {
|
||||
proxyInfo = mOpEventProxyInfoPool.acquire(proxyUid, proxyPackageName,
|
||||
proxyAttributionTag);
|
||||
}
|
||||
|
||||
if (recycled != null) {
|
||||
recycled.reinit(startTime, elapsedTime, clientId, attributionTag, onDeath,
|
||||
uidState, flags, proxyInfo, attributionFlags, attributionChainId,
|
||||
mOpEventProxyInfoPool);
|
||||
return recycled;
|
||||
}
|
||||
|
||||
return new InProgressStartOpEvent(startTime, elapsedTime, clientId, attributionTag,
|
||||
onDeath, uidState, proxyInfo, flags, attributionFlags, attributionChainId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An unsynchronized pool of {@link AppOpsManager.OpEventProxyInfo} objects.
|
||||
*/
|
||||
static class OpEventProxyInfoPool extends Pools.SimplePool<AppOpsManager.OpEventProxyInfo> {
|
||||
OpEventProxyInfoPool(int maxUnusedPooledObjects) {
|
||||
super(maxUnusedPooledObjects);
|
||||
}
|
||||
|
||||
AppOpsManager.OpEventProxyInfo acquire(@IntRange(from = 0) int uid,
|
||||
@Nullable String packageName,
|
||||
@Nullable String attributionTag) {
|
||||
AppOpsManager.OpEventProxyInfo recycled = acquire();
|
||||
if (recycled != null) {
|
||||
recycled.reinit(uid, packageName, attributionTag);
|
||||
return recycled;
|
||||
}
|
||||
|
||||
return new AppOpsManager.OpEventProxyInfo(uid, packageName, attributionTag);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user