Merge "Add attribution info to start callbacks" into sc-dev am: 5d97f29218
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/15446590 Change-Id: If5ddeb244befcaea57a3e53f741b94ec5bfd6dd0
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 */
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user