blob: dcc36bcf6149cbde873006c1b10192867a2aa513 [file] [log] [blame]
/*
* 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);
}
}
}