diff options
3 files changed, 80 insertions, 7 deletions
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index b48a8fb73832..1dddf064e82a 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -1461,9 +1461,25 @@ public class AppOpsManager { */ public static final int OP_USE_FULL_SCREEN_INTENT = AppProtoEnums.APP_OP_USE_FULL_SCREEN_INTENT; + /** + * Hides camera indicator for sandboxed detection apps that directly access the service. + * + * @hide + */ + public static final int OP_CAMERA_SANDBOXED = + AppProtoEnums.APP_OP_CAMERA_SANDBOXED; + + /** + * Hides microphone indicator for sandboxed detection apps that directly access the service. + * + * @hide + */ + public static final int OP_RECORD_AUDIO_SANDBOXED = + AppProtoEnums.APP_OP_RECORD_AUDIO_SANDBOXED; + /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static final int _NUM_OP = 134; + public static final int _NUM_OP = 136; /** * All app ops represented as strings. @@ -1605,6 +1621,8 @@ public class AppOpsManager { OPSTR_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD, OPSTR_BODY_SENSORS_WRIST_TEMPERATURE, OPSTR_USE_FULL_SCREEN_INTENT, + OPSTR_CAMERA_SANDBOXED, + OPSTR_RECORD_AUDIO_SANDBOXED }) public @interface AppOpString {} @@ -2013,6 +2031,20 @@ public class AppOpsManager { public static final String OPSTR_COARSE_LOCATION_SOURCE = "android:coarse_location_source"; /** + * Camera is being recorded in sandboxed detection process. + * + * @hide + */ + public static final String OPSTR_CAMERA_SANDBOXED = "android:camera_sandboxed"; + + /** + * Audio is being recorded in sandboxed detection process. + * + * @hide + */ + public static final String OPSTR_RECORD_AUDIO_SANDBOXED = "android:record_audio_sandboxed"; + + /** * Allow apps to create the requests to manage the media files without user confirmation. * * @see android.Manifest.permission#MANAGE_MEDIA @@ -2738,7 +2770,11 @@ public class AppOpsManager { .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(), new AppOpInfo.Builder(OP_USE_FULL_SCREEN_INTENT, OPSTR_USE_FULL_SCREEN_INTENT, "USE_FULL_SCREEN_INTENT").setPermission(Manifest.permission.USE_FULL_SCREEN_INTENT) - .build() + .build(), + new AppOpInfo.Builder(OP_CAMERA_SANDBOXED, OPSTR_CAMERA_SANDBOXED, + "CAMERA_SANDBOXED").setDefaultMode(AppOpsManager.MODE_ALLOWED).build(), + new AppOpInfo.Builder(OP_RECORD_AUDIO_SANDBOXED, OPSTR_RECORD_AUDIO_SANDBOXED, + "RECORD_AUDIO_SANDBOXED").setDefaultMode(AppOpsManager.MODE_ALLOWED).build() }; // The number of longs needed to form a full bitmask of app ops diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 81ba4b813de1..a110169ac8c2 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -34,6 +34,7 @@ import static android.app.AppOpsManager.MODE_ERRORED; import static android.app.AppOpsManager.MODE_FOREGROUND; import static android.app.AppOpsManager.MODE_IGNORED; import static android.app.AppOpsManager.OP_CAMERA; +import static android.app.AppOpsManager.OP_CAMERA_SANDBOXED; import static android.app.AppOpsManager.OP_FLAGS_ALL; import static android.app.AppOpsManager.OP_FLAG_SELF; import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED; @@ -42,6 +43,7 @@ import static android.app.AppOpsManager.OP_PLAY_AUDIO; import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_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_SANDBOXED; import static android.app.AppOpsManager.OP_VIBRATE; import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_FAILED; import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_STARTED; @@ -3027,17 +3029,29 @@ public class AppOpsService extends IAppOpsService.Stub { packageName); } - // As a special case for OP_RECORD_AUDIO_HOTWORD, which we use only for attribution - // purposes and not as a check, also make sure that the caller is allowed to access - // the data gated by OP_RECORD_AUDIO. + // As a special case for OP_RECORD_AUDIO_HOTWORD, OP_RECEIVE_AMBIENT_TRIGGER_AUDIO and + // OP_RECORD_AUDIO_SANDBOXED which we use only for attribution purposes and not as a check, + // also make sure that the caller is allowed to access the data gated by OP_RECORD_AUDIO. // // TODO: Revert this change before Android 12. - if (code == OP_RECORD_AUDIO_HOTWORD || code == OP_RECEIVE_AMBIENT_TRIGGER_AUDIO) { - int result = checkOperation(OP_RECORD_AUDIO, uid, packageName); + int result = MODE_DEFAULT; + if (code == OP_RECORD_AUDIO_HOTWORD || code == OP_RECEIVE_AMBIENT_TRIGGER_AUDIO + || code == OP_RECORD_AUDIO_SANDBOXED) { + result = checkOperation(OP_RECORD_AUDIO, uid, packageName); + // Check result if (result != AppOpsManager.MODE_ALLOWED) { return new SyncNotedAppOp(result, code, attributionTag, packageName); } } + // As a special case for OP_CAMERA_SANDBOXED. + if (code == OP_CAMERA_SANDBOXED) { + result = checkOperation(OP_CAMERA, uid, packageName); + // Check result + if (result != AppOpsManager.MODE_ALLOWED) { + return new SyncNotedAppOp(result, code, attributionTag, packageName); + } + } + return startOperationUnchecked(clientId, code, uid, packageName, attributionTag, Process.INVALID_UID, null, null, OP_FLAG_SELF, startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage, attributionFlags, diff --git a/services/core/java/com/android/server/policy/AppOpsPolicy.java b/services/core/java/com/android/server/policy/AppOpsPolicy.java index 401eac6e2d19..7a5664f8135e 100644 --- a/services/core/java/com/android/server/policy/AppOpsPolicy.java +++ b/services/core/java/com/android/server/policy/AppOpsPolicy.java @@ -316,6 +316,7 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat private int resolveDatasourceOp(int code, int uid, @NonNull String packageName, @Nullable String attributionTag) { code = resolveRecordAudioOp(code, uid); + code = resolveSandboxedServiceOp(code, uid); if (attributionTag == null) { return code; } @@ -439,6 +440,28 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat return code; } + private int resolveSandboxedServiceOp(int code, int uid) { + if (!Process.isIsolated(uid) // simple check which fails-fast for the common case + || !(code == AppOpsManager.OP_RECORD_AUDIO || code == AppOpsManager.OP_CAMERA)) { + return code; + } + final HotwordDetectionServiceIdentity hotwordDetectionServiceIdentity = + mVoiceInteractionManagerInternal.getHotwordDetectionServiceIdentity(); + if (hotwordDetectionServiceIdentity != null + && uid == hotwordDetectionServiceIdentity.getIsolatedUid()) { + // Upgrade the op such that no indicators is shown for camera or audio service. This + // will bypass the permission checking for the original OP_RECORD_AUDIO and OP_CAMERA. + switch (code) { + case AppOpsManager.OP_RECORD_AUDIO: + return AppOpsManager.OP_RECORD_AUDIO_SANDBOXED; + case AppOpsManager.OP_CAMERA: + return AppOpsManager.OP_CAMERA_SANDBOXED; + } + } + return code; + } + + private int resolveUid(int code, int uid) { // The HotwordDetectionService is an isolated service, which ordinarily cannot hold // permissions. So we allow it to assume the owning package identity for certain |