diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 755e1037ea6b4..7a22e373045d6 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -587,6 +587,7 @@ package android.app { field public static final String OPSTR_READ_MEDIA_VIDEO = "android:read_media_video"; field public static final String OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO = "android:receive_ambient_trigger_audio"; field public static final String OPSTR_RECEIVE_EMERGENCY_BROADCAST = "android:receive_emergency_broadcast"; + field public static final String OPSTR_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO = "android:receive_explicit_user_interaction_audio"; field public static final String OPSTR_REQUEST_DELETE_PACKAGES = "android:request_delete_packages"; field public static final String OPSTR_REQUEST_INSTALL_PACKAGES = "android:request_install_packages"; field public static final String OPSTR_RUN_ANY_IN_BACKGROUND = "android:run_any_in_background"; diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 0cb00d98b3b41..3f39026c81896 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -25,6 +25,7 @@ import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; @@ -1342,9 +1343,19 @@ public class AppOpsManager { public static final int OP_RECEIVE_AMBIENT_TRIGGER_AUDIO = AppProtoEnums.APP_OP_RECEIVE_AMBIENT_TRIGGER_AUDIO; + /** + * Receive audio from near-field mic (ie. TV remote) + * Allows audio recording regardless of sensor privacy state, + * as it is an intentional user interaction: hold-to-talk + * + * @hide + */ + public static final int OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO = + AppProtoEnums.APP_OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO; + /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static final int _NUM_OP = 121; + public static final int _NUM_OP = 122; /** Access to coarse location information. */ public static final String OPSTR_COARSE_LOCATION = "android:coarse_location"; @@ -1816,6 +1827,18 @@ public class AppOpsManager { public static final String OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO = "android:receive_ambient_trigger_audio"; + /** + * Record audio from near-field microphone (ie. TV remote) + * Allows audio recording regardless of sensor privacy state, + * as it is an intentional user interaction: hold-to-talk + * + * @hide + */ + @SystemApi + @SuppressLint("IntentName") + public static final String OPSTR_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO = + "android:receive_explicit_user_interaction_audio"; + /** {@link #sAppOpsToNote} not initialized yet for this op */ private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0; /** Should not collect noting of this app-op in {@link #sAppOpsToNote} */ @@ -2285,7 +2308,11 @@ public class AppOpsManager { .setDisableReset(true).setRestrictRead(true).build(), new AppOpInfo.Builder(OP_RECEIVE_AMBIENT_TRIGGER_AUDIO, OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO, "RECEIVE_SOUNDTRIGGER_AUDIO").setDefaultMode(AppOpsManager.MODE_ALLOWED) - .setForceCollectNotes(true).build() + .setForceCollectNotes(true).build(), + new AppOpInfo.Builder(OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, + OPSTR_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, + "RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO").setDefaultMode( + AppOpsManager.MODE_ALLOWED).build() }; /** diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 29e24598f8742..cd2bbebf3d4d9 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -16043,6 +16043,18 @@ public final class Settings { public static final String KEY_CHORD_POWER_VOLUME_UP = "key_chord_power_volume_up"; + /** + * Record audio from near-field microphone (ie. TV remote) + * Allows audio recording regardless of sensor privacy state, + * as it is an intentional user interaction: hold-to-talk + * + * Type: int (0 to disable, 1 to enable) + * + * @hide + */ + public static final String RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO_ENABLED = + "receive_explicit_user_interaction_audio_enabled"; + /** * Keyguard should be on the left hand side of the screen, for wide screen layouts. * diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java index 298bbbdda89bc..28286817ea7e0 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java @@ -84,6 +84,7 @@ public class GlobalSettings { Settings.Global.USER_PREFERRED_RESOLUTION_HEIGHT, Settings.Global.USER_PREFERRED_RESOLUTION_WIDTH, Settings.Global.POWER_BUTTON_LONG_PRESS, + Settings.Global.RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO_ENABLED, Settings.Global.Wearable.SMART_REPLIES_ENABLED, Settings.Global.Wearable.CLOCKWORK_AUTO_TIME, Settings.Global.Wearable.CLOCKWORK_AUTO_TIME_ZONE, diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java index 9ef6d8fb358a1..e30dd2fad357c 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java @@ -329,6 +329,8 @@ public class GlobalSettingsValidators { VALIDATORS.put(Global.USER_PREFERRED_REFRESH_RATE, NON_NEGATIVE_FLOAT_VALIDATOR); VALIDATORS.put(Global.USER_PREFERRED_RESOLUTION_HEIGHT, ANY_INTEGER_VALIDATOR); VALIDATORS.put(Global.USER_PREFERRED_RESOLUTION_WIDTH, ANY_INTEGER_VALIDATOR); + VALIDATORS.put(Global.RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO_ENABLED, + new DiscreteValueValidator(new String[]{"0", "1"})); VALIDATORS.put(Global.Wearable.WET_MODE_ON, BOOLEAN_VALIDATOR); VALIDATORS.put(Global.Wearable.COOLDOWN_MODE_ON, BOOLEAN_VALIDATOR); VALIDATORS.put( diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java index 6b859763eb6fd..6785a43600f24 100644 --- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java @@ -103,6 +103,7 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon AppOpsManager.OP_SYSTEM_ALERT_WINDOW, AppOpsManager.OP_RECORD_AUDIO, AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO, + AppOpsManager.OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, AppOpsManager.OP_PHONE_CALL_MICROPHONE, AppOpsManager.OP_COARSE_LOCATION, AppOpsManager.OP_FINE_LOCATION diff --git a/packages/SystemUI/src/com/android/systemui/privacy/AppOpsPrivacyItemMonitor.kt b/packages/SystemUI/src/com/android/systemui/privacy/AppOpsPrivacyItemMonitor.kt index de34cd6b23ca7..88b8676ae1c00 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/AppOpsPrivacyItemMonitor.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/AppOpsPrivacyItemMonitor.kt @@ -56,7 +56,8 @@ class AppOpsPrivacyItemMonitor @Inject constructor( val OPS_MIC_CAMERA = intArrayOf(AppOpsManager.OP_CAMERA, AppOpsManager.OP_PHONE_CALL_CAMERA, AppOpsManager.OP_RECORD_AUDIO, AppOpsManager.OP_PHONE_CALL_MICROPHONE, - AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO) + AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO, + AppOpsManager.OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO) val OPS_LOCATION = intArrayOf( AppOpsManager.OP_COARSE_LOCATION, AppOpsManager.OP_FINE_LOCATION) @@ -210,6 +211,7 @@ class AppOpsPrivacyItemMonitor @Inject constructor( AppOpsManager.OP_FINE_LOCATION -> PrivacyType.TYPE_LOCATION AppOpsManager.OP_PHONE_CALL_MICROPHONE, AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO, + AppOpsManager.OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, AppOpsManager.OP_RECORD_AUDIO -> PrivacyType.TYPE_MICROPHONE else -> return null } diff --git a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java index c79bc89efbe97..61c21e6b4d703 100644 --- a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java +++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java @@ -26,6 +26,7 @@ import static android.app.AppOpsManager.OP_CAMERA; import static android.app.AppOpsManager.OP_PHONE_CALL_CAMERA; import static android.app.AppOpsManager.OP_PHONE_CALL_MICROPHONE; import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO; +import static android.app.AppOpsManager.OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO; import static android.app.AppOpsManager.OP_RECORD_AUDIO; import static android.content.Intent.EXTRA_PACKAGE_NAME; import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; @@ -81,6 +82,7 @@ import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.res.Configuration; +import android.database.ContentObserver; import android.graphics.drawable.Icon; import android.hardware.ISensorPrivacyListener; import android.hardware.ISensorPrivacyManager; @@ -199,6 +201,7 @@ public final class SensorPrivacyService extends SystemService { if (phase == PHASE_SYSTEM_SERVICES_READY) { mKeyguardManager = mContext.getSystemService(KeyguardManager.class); mCallStateHelper = new CallStateHelper(); + mSensorPrivacyServiceImpl.registerSettingsObserver(); } else if (phase == PHASE_ACTIVITY_MANAGER_READY) { mCameraPrivacyLightController = new CameraPrivacyLightController(mContext); } @@ -271,7 +274,7 @@ public final class SensorPrivacyService extends SystemService { mSensorPrivacyStateController = SensorPrivacyStateController.getInstance(); int[] micAndCameraOps = new int[]{OP_RECORD_AUDIO, OP_PHONE_CALL_MICROPHONE, - OP_CAMERA, OP_PHONE_CALL_CAMERA}; + OP_CAMERA, OP_PHONE_CALL_CAMERA, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO}; mAppOpsManager.startWatchingNoted(micAndCameraOps, this); mAppOpsManager.startWatchingStarted(micAndCameraOps, this); @@ -340,7 +343,8 @@ public final class SensorPrivacyService extends SystemService { int sensor; if (result == MODE_IGNORED) { - if (code == OP_RECORD_AUDIO || code == OP_PHONE_CALL_MICROPHONE) { + if (code == OP_RECORD_AUDIO || code == OP_PHONE_CALL_MICROPHONE + || code == OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO) { sensor = MICROPHONE; } else if (code == OP_CAMERA || code == OP_PHONE_CALL_CAMERA) { sensor = CAMERA; @@ -1072,6 +1076,14 @@ public final class SensorPrivacyService extends SystemService { // restrict it when the microphone is disabled mAppOpsManagerInternal.setGlobalRestriction(OP_RECEIVE_AMBIENT_TRIGGER_AUDIO, enabled, mAppOpsRestrictionToken); + + // Set restriction for OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO + boolean allowed = (Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO_ENABLED, 1) + == 1); + mAppOpsManagerInternal.setGlobalRestriction( + OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, enabled && !allowed, + mAppOpsRestrictionToken); break; case CAMERA: mAppOpsManagerInternal.setGlobalRestriction(OP_CAMERA, enabled, @@ -1112,6 +1124,19 @@ public final class SensorPrivacyService extends SystemService { } } + private void registerSettingsObserver() { + mContext.getContentResolver().registerContentObserver( + Settings.Global.getUriFor( + Settings.Global.RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO_ENABLED), + false, new ContentObserver(mHandler) { + @Override + public void onChange(boolean selfChange) { + setGlobalRestriction(MICROPHONE, + isCombinedToggleSensorPrivacyEnabled(MICROPHONE)); + } + }); + } + /** * A owner of a suppressor token died. Clean up. *