Merge "Add attribution info to start callbacks" into sc-dev

This commit is contained in:
Nate Myren
2021-08-03 19:56:02 +00:00
committed by Android (Google) Code Review
5 changed files with 163 additions and 27 deletions

View File

@@ -7205,6 +7205,34 @@ public class AppOpsManager {
* @hide * @hide
*/ */
public interface OnOpStartedListener { public interface OnOpStartedListener {
/**
* Represents a start operation that was unsuccessful
* @hide
*/
public int START_TYPE_FAILED = 0;
/**
* Represents a successful start operation
* @hide
*/
public int START_TYPE_STARTED = 1;
/**
* Represents an operation where a restricted operation became unrestricted, and resumed.
* @hide
*/
public int START_TYPE_RESUMED = 2;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, prefix = { "TYPE_" }, value = {
START_TYPE_FAILED,
START_TYPE_STARTED,
START_TYPE_RESUMED
})
public @interface StartedType {}
/** /**
* Called when an op was started. * Called when an op was started.
* *
@@ -7213,11 +7241,35 @@ public class AppOpsManager {
* @param uid The UID performing the operation. * @param uid The UID performing the operation.
* @param packageName The package performing the operation. * @param packageName The package performing the operation.
* @param attributionTag The attribution tag performing the operation. * @param attributionTag The attribution tag performing the operation.
* @param flags The flags of this op * @param flags The flags of this op.
* @param result The result of the start. * @param result The result of the start.
*/ */
void onOpStarted(int op, int uid, String packageName, String attributionTag, void onOpStarted(int op, int uid, String packageName, String attributionTag,
@OpFlags int flags, @Mode int result); @OpFlags int flags, @Mode int result);
/**
* Called when an op was started.
*
* Note: This is only for op starts. It is not called when an op is noted or stopped.
* By default, unless this method is overridden, no code will be executed for resume
* events.
* @param op The op code.
* @param uid The UID performing the operation.
* @param packageName The package performing the operation.
* @param attributionTag The attribution tag performing the operation.
* @param flags The flags of this op.
* @param result The result of the start.
* @param startType The start type of this start event. Either failed, resumed, or started.
* @param attributionFlags The location of this started op in an attribution chain.
* @param attributionChainId The ID of the attribution chain of this op, if it is in one.
*/
default void onOpStarted(int op, int uid, String packageName, String attributionTag,
@OpFlags int flags, @Mode int result, @StartedType int startType,
@AttributionFlags int attributionFlags, int attributionChainId) {
if (startType != START_TYPE_RESUMED) {
onOpStarted(op, uid, packageName, attributionTag, flags, result);
}
}
} }
AppOpsManager(Context context, IAppOpsService service) { AppOpsManager(Context context, IAppOpsService service) {
@@ -7858,8 +7910,10 @@ public class AppOpsManager {
cb = new IAppOpsStartedCallback.Stub() { cb = new IAppOpsStartedCallback.Stub() {
@Override @Override
public void opStarted(int op, int uid, String packageName, String attributionTag, public void opStarted(int op, int uid, String packageName, String attributionTag,
int flags, int mode) { int flags, int mode, int startType, int attributionFlags,
callback.onOpStarted(op, uid, packageName, attributionTag, flags, mode); int attributionChainId) {
callback.onOpStarted(op, uid, packageName, attributionTag, flags, mode,
startType, attributionFlags, attributionChainId);
} }
}; };
mStartedWatchers.put(callback, cb); mStartedWatchers.put(callback, cb);

View File

@@ -31,7 +31,9 @@ import static android.app.AppOpsManager.OPSTR_FINE_LOCATION;
import static android.app.AppOpsManager.OPSTR_PHONE_CALL_CAMERA; import static android.app.AppOpsManager.OPSTR_PHONE_CALL_CAMERA;
import static android.app.AppOpsManager.OPSTR_PHONE_CALL_MICROPHONE; import static android.app.AppOpsManager.OPSTR_PHONE_CALL_MICROPHONE;
import static android.app.AppOpsManager.OPSTR_RECORD_AUDIO; import static android.app.AppOpsManager.OPSTR_RECORD_AUDIO;
import static android.app.AppOpsManager.OP_CAMERA;
import static android.app.AppOpsManager.OP_FLAGS_ALL_TRUSTED; import static android.app.AppOpsManager.OP_FLAGS_ALL_TRUSTED;
import static android.app.AppOpsManager.OP_RECORD_AUDIO;
import static android.media.AudioSystem.MODE_IN_COMMUNICATION; import static android.media.AudioSystem.MODE_IN_COMMUNICATION;
import static android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS; import static android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
@@ -63,7 +65,8 @@ import java.util.Objects;
* *
* @hide * @hide
*/ */
public class PermissionUsageHelper implements AppOpsManager.OnOpActiveChangedListener { public class PermissionUsageHelper implements AppOpsManager.OnOpActiveChangedListener,
AppOpsManager.OnOpStartedListener {
/** Whether to show the mic and camera icons. */ /** Whether to show the mic and camera icons. */
private static final String PROPERTY_CAMERA_MIC_ICONS_ENABLED = "camera_mic_icons_enabled"; private static final String PROPERTY_CAMERA_MIC_ICONS_ENABLED = "camera_mic_icons_enabled";
@@ -160,9 +163,10 @@ public class PermissionUsageHelper implements AppOpsManager.OnOpActiveChangedLis
mUserContexts = new ArrayMap<>(); mUserContexts = new ArrayMap<>();
mUserContexts.put(Process.myUserHandle(), mContext); mUserContexts.put(Process.myUserHandle(), mContext);
// TODO ntmyren: make this listen for flag enable/disable changes // TODO ntmyren: make this listen for flag enable/disable changes
String[] ops = { OPSTR_CAMERA, OPSTR_RECORD_AUDIO }; String[] opStrs = { OPSTR_CAMERA, OPSTR_RECORD_AUDIO };
mContext.getSystemService(AppOpsManager.class).startWatchingActive(ops, mAppOpsManager.startWatchingActive(opStrs, context.getMainExecutor(), this);
context.getMainExecutor(), this); int[] ops = { OP_CAMERA, OP_RECORD_AUDIO };
mAppOpsManager.startWatchingStarted(ops, this);
} }
private Context getUserContext(UserHandle user) { private Context getUserContext(UserHandle user) {
@@ -182,25 +186,65 @@ public class PermissionUsageHelper implements AppOpsManager.OnOpActiveChangedLis
public void onOpActiveChanged(@NonNull String op, int uid, @NonNull String packageName, public void onOpActiveChanged(@NonNull String op, int uid, @NonNull String packageName,
@Nullable String attributionTag, boolean active, @AttributionFlags int attributionFlags, @Nullable String attributionTag, boolean active, @AttributionFlags int attributionFlags,
int attributionChainId) { int attributionChainId) {
if (attributionChainId == ATTRIBUTION_CHAIN_ID_NONE if (active) {
|| attributionFlags == ATTRIBUTION_FLAGS_NONE // Started callback handles these
|| (attributionFlags & ATTRIBUTION_FLAG_TRUSTED) == 0) {
// If this is not a chain, or it is untrusted, return
return; return;
} }
if (!active) { // if any link in the chain is finished, remove the chain. Then, find any other chains that
// if any link in the chain is finished, remove the chain. // contain this op/package/uid/tag combination, and remove them, as well.
// TODO ntmyren: be smarter about this // TODO ntmyren: be smarter about this
mAttributionChains.remove(attributionChainId); mAttributionChains.remove(attributionChainId);
int numChains = mAttributionChains.size();
ArrayList<Integer> toRemove = new ArrayList<>();
for (int i = 0; i < numChains; i++) {
int chainId = mAttributionChains.keyAt(i);
ArrayList<AccessChainLink> chain = mAttributionChains.valueAt(i);
int chainSize = chain.size();
for (int j = 0; j < chainSize; j++) {
AccessChainLink link = chain.get(j);
if (link.packageAndOpEquals(op, packageName, attributionTag, uid)) {
toRemove.add(chainId);
break;
}
}
}
mAttributionChains.removeAll(toRemove);
}
@Override
public void onOpStarted(int op, int uid, String packageName, String attributionTag,
@AppOpsManager.OpFlags int flags, @AppOpsManager.Mode int result) {
// not part of an attribution chain. Do nothing
}
@Override
public void onOpStarted(int op, int uid, String packageName, String attributionTag,
@AppOpsManager.OpFlags int flags, @AppOpsManager.Mode int result,
@StartedType int startedType, @AttributionFlags int attributionFlags,
int attributionChainId) {
if (startedType == START_TYPE_FAILED || attributionChainId == ATTRIBUTION_CHAIN_ID_NONE
|| attributionFlags == ATTRIBUTION_FLAGS_NONE
|| (attributionFlags & ATTRIBUTION_FLAG_TRUSTED) == 0) {
// If this is not a successful start, or it is not a chain, or it is untrusted, return
return; return;
} }
addLinkToChainIfNotPresent(AppOpsManager.opToPublicName(op), packageName, uid,
attributionTag, attributionFlags, attributionChainId);
}
private void addLinkToChainIfNotPresent(String op, String packageName, int uid,
String attributionTag, int attributionFlags, int attributionChainId) {
ArrayList<AccessChainLink> currentChain = mAttributionChains.computeIfAbsent( ArrayList<AccessChainLink> currentChain = mAttributionChains.computeIfAbsent(
attributionChainId, k -> new ArrayList<>()); attributionChainId, k -> new ArrayList<>());
AccessChainLink link = new AccessChainLink(op, packageName, attributionTag, uid, AccessChainLink link = new AccessChainLink(op, packageName, attributionTag, uid,
attributionFlags); attributionFlags);
if (currentChain.contains(link)) {
return;
}
int currSize = currentChain.size(); int currSize = currentChain.size();
if (currSize == 0 || link.isEnd() || !currentChain.get(currSize - 1).isEnd()) { if (currSize == 0 || link.isEnd() || !currentChain.get(currSize - 1).isEnd()) {
// if the list is empty, this link is the end, or the last link in the current chain // if the list is empty, this link is the end, or the last link in the current chain
@@ -613,5 +657,21 @@ public class PermissionUsageHelper implements AppOpsManager.OnOpActiveChangedLis
public boolean isStart() { public boolean isStart() {
return (flags & ATTRIBUTION_FLAG_RECEIVER) != 0; return (flags & ATTRIBUTION_FLAG_RECEIVER) != 0;
} }
@Override
public boolean equals(Object obj) {
if (!(obj instanceof AccessChainLink)) {
return false;
}
AccessChainLink other = (AccessChainLink) obj;
return other.flags == flags && packageAndOpEquals(other.usage.op,
other.usage.packageName, other.usage.attributionTag, other.usage.uid);
}
public boolean packageAndOpEquals(String op, String packageName, String attributionTag,
int uid) {
return Objects.equals(op, usage.op) && Objects.equals(packageName, usage.packageName)
&& Objects.equals(attributionTag, usage.attributionTag) && uid == usage.uid;
}
} }
} }

View File

@@ -18,5 +18,6 @@ package com.android.internal.app;
// Iterface to observe op starts // Iterface to observe op starts
oneway interface IAppOpsStartedCallback { oneway interface IAppOpsStartedCallback {
void opStarted(int op, int uid, String packageName, String attributionTag, int flags, int mode); void opStarted(int op, int uid, String packageName, String attributionTag, int flags, int mode,
int startedType, int attributionFlags, int attributionChainId);
} }

View File

@@ -45,6 +45,9 @@ import static android.app.AppOpsManager.OP_NONE;
import static android.app.AppOpsManager.OP_PLAY_AUDIO; import static android.app.AppOpsManager.OP_PLAY_AUDIO;
import static android.app.AppOpsManager.OP_RECORD_AUDIO; import static android.app.AppOpsManager.OP_RECORD_AUDIO;
import static android.app.AppOpsManager.OP_RECORD_AUDIO_HOTWORD; import static android.app.AppOpsManager.OP_RECORD_AUDIO_HOTWORD;
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.OpEventProxyInfo;
import static android.app.AppOpsManager.RestrictionBypass; import static android.app.AppOpsManager.RestrictionBypass;
import static android.app.AppOpsManager.SAMPLING_STRATEGY_BOOT_TIME_SAMPLING; import static android.app.AppOpsManager.SAMPLING_STRATEGY_BOOT_TIME_SAMPLING;
@@ -1238,6 +1241,11 @@ public class AppOpsService extends IAppOpsService.Stub {
scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid, parent.packageName, scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid, parent.packageName,
tag, true, event.getAttributionFlags(), event.getAttributionChainId()); 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; mPausedInProgressEvents = null;
} }
@@ -3945,13 +3953,15 @@ public class AppOpsService extends IAppOpsService.Stub {
} }
boolean isRestricted = false; boolean isRestricted = false;
int startType = START_TYPE_FAILED;
synchronized (this) { synchronized (this) {
final Ops ops = getOpsLocked(uid, packageName, attributionTag, final Ops ops = getOpsLocked(uid, packageName, attributionTag,
pvr.isAttributionTagValid, pvr.bypass, /* edit */ true); pvr.isAttributionTagValid, pvr.bypass, /* edit */ true);
if (ops == null) { if (ops == null) {
if (!dryRun) { if (!dryRun) {
scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
flags, AppOpsManager.MODE_IGNORED); flags, AppOpsManager.MODE_IGNORED, startType, attributionFlags,
attributionChainId);
} }
if (DEBUG) Slog.d(TAG, "startOperation: no op for code " + code + " uid " + uid if (DEBUG) Slog.d(TAG, "startOperation: no op for code " + code + " uid " + uid
+ " package " + packageName + " flags: " + " package " + packageName + " flags: "
@@ -3977,7 +3987,7 @@ public class AppOpsService extends IAppOpsService.Stub {
if (!dryRun) { if (!dryRun) {
attributedOp.rejected(uidState.state, flags); attributedOp.rejected(uidState.state, flags);
scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
flags, uidMode); flags, uidMode, startType, attributionFlags, attributionChainId);
} }
return new SyncNotedAppOp(uidMode, code, attributionTag, packageName); return new SyncNotedAppOp(uidMode, code, attributionTag, packageName);
} }
@@ -3993,7 +4003,7 @@ public class AppOpsService extends IAppOpsService.Stub {
if (!dryRun) { if (!dryRun) {
attributedOp.rejected(uidState.state, flags); attributedOp.rejected(uidState.state, flags);
scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
flags, mode); flags, mode, startType, attributionFlags, attributionChainId);
} }
return new SyncNotedAppOp(mode, code, attributionTag, packageName); return new SyncNotedAppOp(mode, code, attributionTag, packageName);
} }
@@ -4011,12 +4021,14 @@ public class AppOpsService extends IAppOpsService.Stub {
attributedOp.started(clientId, proxyUid, proxyPackageName, attributedOp.started(clientId, proxyUid, proxyPackageName,
proxyAttributionTag, uidState.state, flags, attributionFlags, proxyAttributionTag, uidState.state, flags, attributionFlags,
attributionChainId); attributionChainId);
startType = START_TYPE_STARTED;
} }
} catch (RemoteException e) { } catch (RemoteException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, flags, scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, flags,
isRestricted ? MODE_IGNORED : MODE_ALLOWED); isRestricted ? MODE_IGNORED : MODE_ALLOWED, startType, attributionFlags,
attributionChainId);
} }
} }
@@ -4187,7 +4199,9 @@ public class AppOpsService extends IAppOpsService.Stub {
} }
private void scheduleOpStartedIfNeededLocked(int code, int uid, String pkgName, private void scheduleOpStartedIfNeededLocked(int code, int uid, String pkgName,
String attributionTag, @OpFlags int flags, @Mode int result) { String attributionTag, @OpFlags int flags, @Mode int result,
@AppOpsManager.OnOpStartedListener.StartedType int startedType,
@AttributionFlags int attributionFlags, int attributionChainId) {
ArraySet<StartedCallback> dispatchedCallbacks = null; ArraySet<StartedCallback> dispatchedCallbacks = null;
final int callbackListCount = mStartedWatchers.size(); final int callbackListCount = mStartedWatchers.size();
for (int i = 0; i < callbackListCount; i++) { for (int i = 0; i < callbackListCount; i++) {
@@ -4213,12 +4227,13 @@ public class AppOpsService extends IAppOpsService.Stub {
mHandler.sendMessage(PooledLambda.obtainMessage( mHandler.sendMessage(PooledLambda.obtainMessage(
AppOpsService::notifyOpStarted, AppOpsService::notifyOpStarted,
this, dispatchedCallbacks, code, uid, pkgName, attributionTag, flags, this, dispatchedCallbacks, code, uid, pkgName, attributionTag, flags,
result)); result, startedType, attributionFlags, attributionChainId));
} }
private void notifyOpStarted(ArraySet<StartedCallback> callbacks, private void notifyOpStarted(ArraySet<StartedCallback> callbacks,
int code, int uid, String packageName, String attributionTag, @OpFlags int flags, int code, int uid, String packageName, String attributionTag, @OpFlags int flags,
@Mode int result) { @Mode int result, @AppOpsManager.OnOpStartedListener.StartedType int startedType,
@AttributionFlags int attributionFlags, int attributionChainId) {
final long identity = Binder.clearCallingIdentity(); final long identity = Binder.clearCallingIdentity();
try { try {
final int callbackCount = callbacks.size(); final int callbackCount = callbacks.size();
@@ -4226,7 +4241,7 @@ public class AppOpsService extends IAppOpsService.Stub {
final StartedCallback callback = callbacks.valueAt(i); final StartedCallback callback = callbacks.valueAt(i);
try { try {
callback.mCallback.opStarted(code, uid, packageName, attributionTag, flags, callback.mCallback.opStarted(code, uid, packageName, attributionTag, flags,
result); result, startedType, attributionFlags, attributionChainId);
} catch (RemoteException e) { } catch (RemoteException e) {
/* do nothing */ /* do nothing */
} }

View File

@@ -64,12 +64,16 @@ public class AppOpsStartedWatcherTest {
.times(1)).onOpStarted(eq(AppOpsManager.OP_FINE_LOCATION), .times(1)).onOpStarted(eq(AppOpsManager.OP_FINE_LOCATION),
eq(Process.myUid()), eq(getContext().getPackageName()), eq(Process.myUid()), eq(getContext().getPackageName()),
eq(getContext().getAttributionTag()), eq(AppOpsManager.OP_FLAG_SELF), eq(getContext().getAttributionTag()), eq(AppOpsManager.OP_FLAG_SELF),
eq(AppOpsManager.MODE_ALLOWED)); eq(AppOpsManager.MODE_ALLOWED), eq(OnOpStartedListener.START_TYPE_STARTED),
eq(AppOpsManager.ATTRIBUTION_FLAGS_NONE),
eq(AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE));
inOrder.verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS) inOrder.verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
.times(1)).onOpStarted(eq(AppOpsManager.OP_CAMERA), .times(1)).onOpStarted(eq(AppOpsManager.OP_CAMERA),
eq(Process.myUid()), eq(getContext().getPackageName()), eq(Process.myUid()), eq(getContext().getPackageName()),
eq(getContext().getAttributionTag()), eq(AppOpsManager.OP_FLAG_SELF), eq(getContext().getAttributionTag()), eq(AppOpsManager.OP_FLAG_SELF),
eq(AppOpsManager.MODE_ALLOWED)); eq(AppOpsManager.MODE_ALLOWED), eq(OnOpStartedListener.START_TYPE_STARTED),
eq(AppOpsManager.ATTRIBUTION_FLAGS_NONE),
eq(AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE));
// Stop watching // Stop watching
appOpsManager.stopWatchingStarted(listener); appOpsManager.stopWatchingStarted(listener);
@@ -94,7 +98,9 @@ public class AppOpsStartedWatcherTest {
.times(2)).onOpStarted(eq(AppOpsManager.OP_CAMERA), .times(2)).onOpStarted(eq(AppOpsManager.OP_CAMERA),
eq(Process.myUid()), eq(getContext().getPackageName()), eq(Process.myUid()), eq(getContext().getPackageName()),
eq(getContext().getAttributionTag()), eq(AppOpsManager.OP_FLAG_SELF), eq(getContext().getAttributionTag()), eq(AppOpsManager.OP_FLAG_SELF),
eq(AppOpsManager.MODE_ALLOWED)); eq(AppOpsManager.MODE_ALLOWED), eq(OnOpStartedListener.START_TYPE_STARTED),
eq(AppOpsManager.ATTRIBUTION_FLAGS_NONE),
eq(AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE));
verifyNoMoreInteractions(listener); verifyNoMoreInteractions(listener);
// Finish up // Finish up