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
*/
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.
*
@@ -7213,11 +7241,35 @@ public class AppOpsManager {
* @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 flags The flags of this op.
* @param result The result of the start.
*/
void onOpStarted(int op, int uid, String packageName, String attributionTag,
@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) {
@@ -7858,8 +7910,10 @@ public class AppOpsManager {
cb = new IAppOpsStartedCallback.Stub() {
@Override
public void opStarted(int op, int uid, String packageName, String attributionTag,
int flags, int mode) {
callback.onOpStarted(op, uid, packageName, attributionTag, flags, mode);
int flags, int mode, int startType, int attributionFlags,
int attributionChainId) {
callback.onOpStarted(op, uid, packageName, attributionTag, flags, mode,
startType, attributionFlags, attributionChainId);
}
};
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_MICROPHONE;
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_RECORD_AUDIO;
import static android.media.AudioSystem.MODE_IN_COMMUNICATION;
import static android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
@@ -63,7 +65,8 @@ import java.util.Objects;
*
* @hide
*/
public class PermissionUsageHelper implements AppOpsManager.OnOpActiveChangedListener {
public class PermissionUsageHelper implements AppOpsManager.OnOpActiveChangedListener,
AppOpsManager.OnOpStartedListener {
/** Whether to show the mic and camera icons. */
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.put(Process.myUserHandle(), mContext);
// TODO ntmyren: make this listen for flag enable/disable changes
String[] ops = { OPSTR_CAMERA, OPSTR_RECORD_AUDIO };
mContext.getSystemService(AppOpsManager.class).startWatchingActive(ops,
context.getMainExecutor(), this);
String[] opStrs = { OPSTR_CAMERA, OPSTR_RECORD_AUDIO };
mAppOpsManager.startWatchingActive(opStrs, context.getMainExecutor(), this);
int[] ops = { OP_CAMERA, OP_RECORD_AUDIO };
mAppOpsManager.startWatchingStarted(ops, this);
}
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,
@Nullable String attributionTag, boolean active, @AttributionFlags int attributionFlags,
int attributionChainId) {
if (attributionChainId == ATTRIBUTION_CHAIN_ID_NONE
|| attributionFlags == ATTRIBUTION_FLAGS_NONE
|| (attributionFlags & ATTRIBUTION_FLAG_TRUSTED) == 0) {
// If this is not a chain, or it is untrusted, return
if (active) {
// Started callback handles these
return;
}
if (!active) {
// if any link in the chain is finished, remove the chain.
// TODO ntmyren: be smarter about this
mAttributionChains.remove(attributionChainId);
// if any link in the chain is finished, remove the chain. Then, find any other chains that
// contain this op/package/uid/tag combination, and remove them, as well.
// TODO ntmyren: be smarter about this
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;
}
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(
attributionChainId, k -> new ArrayList<>());
AccessChainLink link = new AccessChainLink(op, packageName, attributionTag, uid,
attributionFlags);
if (currentChain.contains(link)) {
return;
}
int currSize = currentChain.size();
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
@@ -613,5 +657,21 @@ public class PermissionUsageHelper implements AppOpsManager.OnOpActiveChangedLis
public boolean isStart() {
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
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_RECORD_AUDIO;
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.RestrictionBypass;
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,
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;
}
@@ -3945,13 +3953,15 @@ public class AppOpsService extends IAppOpsService.Stub {
}
boolean isRestricted = false;
int startType = START_TYPE_FAILED;
synchronized (this) {
final Ops ops = getOpsLocked(uid, packageName, attributionTag,
pvr.isAttributionTagValid, pvr.bypass, /* edit */ true);
if (ops == null) {
if (!dryRun) {
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
+ " package " + packageName + " flags: "
@@ -3977,7 +3987,7 @@ public class AppOpsService extends IAppOpsService.Stub {
if (!dryRun) {
attributedOp.rejected(uidState.state, flags);
scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
flags, uidMode);
flags, uidMode, startType, attributionFlags, attributionChainId);
}
return new SyncNotedAppOp(uidMode, code, attributionTag, packageName);
}
@@ -3993,7 +4003,7 @@ public class AppOpsService extends IAppOpsService.Stub {
if (!dryRun) {
attributedOp.rejected(uidState.state, flags);
scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
flags, mode);
flags, mode, startType, attributionFlags, attributionChainId);
}
return new SyncNotedAppOp(mode, code, attributionTag, packageName);
}
@@ -4011,12 +4021,14 @@ public class AppOpsService extends IAppOpsService.Stub {
attributedOp.started(clientId, proxyUid, proxyPackageName,
proxyAttributionTag, uidState.state, flags, attributionFlags,
attributionChainId);
startType = START_TYPE_STARTED;
}
} catch (RemoteException e) {
throw new RuntimeException(e);
}
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,
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;
final int callbackListCount = mStartedWatchers.size();
for (int i = 0; i < callbackListCount; i++) {
@@ -4213,12 +4227,13 @@ public class AppOpsService extends IAppOpsService.Stub {
mHandler.sendMessage(PooledLambda.obtainMessage(
AppOpsService::notifyOpStarted,
this, dispatchedCallbacks, code, uid, pkgName, attributionTag, flags,
result));
result, startedType, attributionFlags, attributionChainId));
}
private void notifyOpStarted(ArraySet<StartedCallback> callbacks,
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();
try {
final int callbackCount = callbacks.size();
@@ -4226,7 +4241,7 @@ public class AppOpsService extends IAppOpsService.Stub {
final StartedCallback callback = callbacks.valueAt(i);
try {
callback.mCallback.opStarted(code, uid, packageName, attributionTag, flags,
result);
result, startedType, attributionFlags, attributionChainId);
} catch (RemoteException e) {
/* do nothing */
}

View File

@@ -64,12 +64,16 @@ public class AppOpsStartedWatcherTest {
.times(1)).onOpStarted(eq(AppOpsManager.OP_FINE_LOCATION),
eq(Process.myUid()), eq(getContext().getPackageName()),
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)
.times(1)).onOpStarted(eq(AppOpsManager.OP_CAMERA),
eq(Process.myUid()), eq(getContext().getPackageName()),
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
appOpsManager.stopWatchingStarted(listener);
@@ -94,7 +98,9 @@ public class AppOpsStartedWatcherTest {
.times(2)).onOpStarted(eq(AppOpsManager.OP_CAMERA),
eq(Process.myUid()), eq(getContext().getPackageName()),
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);
// Finish up