diff options
5 files changed, 163 insertions, 27 deletions
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index bb7cdfa1a1ea..d932a29beca6 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 6d7966f1eb7c..64b9bd98a2fc 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 |