diff options
| author | 2021-08-02 11:16:14 -0700 | |
|---|---|---|
| committer | 2021-08-03 08:25:31 -0700 | |
| commit | 06d07d54dbc0b67cccf986281e2c7c19f0ae64ca (patch) | |
| tree | 4c28d5616b8576a318d1ee24e1e5a865c1e2d89b | |
| parent | 308cc9919e1dffde75723a1595aa1547e2295b95 (diff) | |
Add attribution info to start callbacks
Add attribution flags and chain IDs to start callbacks, and have the
PermissionUsageHelper listen for starts. This ensures that, if another
start happens while an op is already running, and has chain information,
then this chain information will be recorded.
Test: manual
Bug: 194198234
Change-Id: I0ab1aa0969b70e18001f4a814ea5689f9329a019
5 files changed, 163 insertions, 27 deletions
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 4ddb546a351e..d1b0b9b83ace 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -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); diff --git a/core/java/android/permission/PermissionUsageHelper.java b/core/java/android/permission/PermissionUsageHelper.java index 03f94c549512..19f204b377c8 100644 --- a/core/java/android/permission/PermissionUsageHelper.java +++ b/core/java/android/permission/PermissionUsageHelper.java @@ -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; + } } } diff --git a/core/java/com/android/internal/app/IAppOpsStartedCallback.aidl b/core/java/com/android/internal/app/IAppOpsStartedCallback.aidl index 3a108e7e1d94..06640cb4ee06 100644 --- a/core/java/com/android/internal/app/IAppOpsStartedCallback.aidl +++ b/core/java/com/android/internal/app/IAppOpsStartedCallback.aidl @@ -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); } diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index a9905dcf8904..b5cda56ef123 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -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 */ } diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java index c12eb32a9143..e98a4dded99b 100644 --- a/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java +++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java @@ -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 |