diff --git a/core/java/android/hardware/SensorPrivacyManager.java b/core/java/android/hardware/SensorPrivacyManager.java index 99b58c97dc93d..535b551e6dfff 100644 --- a/core/java/android/hardware/SensorPrivacyManager.java +++ b/core/java/android/hardware/SensorPrivacyManager.java @@ -72,6 +72,13 @@ public final class SensorPrivacyManager { public static final String EXTRA_ALL_SENSORS = SensorPrivacyManager.class.getName() + ".extra.all_sensors"; + /** + * An extra containing the sensor type + * @hide + */ + public static final String EXTRA_TOGGLE_TYPE = SensorPrivacyManager.class.getName() + + ".extra.toggle_type"; + /** * Sensor constants which are used in {@link SensorPrivacyManager} */ diff --git a/core/res/res/values-television/config.xml b/core/res/res/values-television/config.xml index 88bf18cb8857f..eaadc20224681 100644 --- a/core/res/res/values-television/config.xml +++ b/core/res/res/values-television/config.xml @@ -54,6 +54,12 @@ com.android.systemui/com.android.systemui.sensorprivacy.television.TvUnblockSensorActivity + + + com.android.systemui/com.android.systemui.sensorprivacy.television.TvSensorPrivacyChangedActivity + + com.android.systemui/com.android.systemui.usb.tv.TvUsbPermissionActivity diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index d86f2108c19a0..7d203355add88 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2944,6 +2944,9 @@ com.android.systemui/com.android.systemui.usb.UsbResolverActivity + + + + diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 4267ba2ff0b77..07e37d3f67d6e 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -557,6 +557,16 @@ android:showForAllUsers="true"> + + + + + + + Microphone turned on + + + + + Microphone turned off + + + + + Microphone is enabled for all apps and services. + + + + + Microphone access is disabled for all apps and services. + You can enable microphone access in Settings > Privacy > Microphone. + + + + + Microphone access is disabled for all apps and services. + You can change this in Settings > Privacy > Microphone. + + + + + Camera turned on + + + + + Camera turned off + + + + + Camera is enabled for all apps and services. + + + + + Camera access is disabled for all apps and services. + + + + To use the microphone button, enable microphone access in Settings. + + + Open settings. + Other device diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java index fb016911f913f..4eb444e3f6522 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java @@ -25,6 +25,7 @@ import com.android.systemui.people.PeopleSpaceActivity; import com.android.systemui.people.widget.LaunchConversationActivity; import com.android.systemui.screenshot.LongScreenshotActivity; import com.android.systemui.sensorprivacy.SensorUseStartedActivity; +import com.android.systemui.sensorprivacy.television.TvSensorPrivacyChangedActivity; import com.android.systemui.sensorprivacy.television.TvUnblockSensorActivity; import com.android.systemui.settings.brightness.BrightnessDialog; import com.android.systemui.statusbar.tv.notifications.TvNotificationPanelActivity; @@ -142,4 +143,11 @@ public abstract class DefaultActivityBinder { @ClassKey(HdmiCecSetMenuLanguageActivity.class) public abstract Activity bindHdmiCecSetMenuLanguageActivity( HdmiCecSetMenuLanguageActivity activity); + + /** Inject into TvSensorPrivacyChangedActivity. */ + @Binds + @IntoMap + @ClassKey(TvSensorPrivacyChangedActivity.class) + public abstract Activity bindTvSensorPrivacyChangedActivity( + TvSensorPrivacyChangedActivity activity); } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java index 139a8b769583a..25418c3f2105a 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java @@ -20,6 +20,7 @@ import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.app.AlarmManager; +import android.app.AppOpsManager; import android.app.IActivityManager; import android.app.IActivityTaskManager; import android.app.INotificationManager; @@ -135,6 +136,12 @@ public class FrameworkServicesModule { return new AmbientDisplayConfiguration(context); } + @Provides + @Singleton + static AppOpsManager provideAppOpsManager(Context context) { + return context.getSystemService(AppOpsManager.class); + } + @Provides @Singleton static AudioManager provideAudioManager(Context context) { diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvSensorPrivacyChangedActivity.java b/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvSensorPrivacyChangedActivity.java new file mode 100644 index 0000000000000..731b177a58a63 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvSensorPrivacyChangedActivity.java @@ -0,0 +1,233 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.sensorprivacy.television; + +import static android.hardware.SensorPrivacyManager.Sensors.CAMERA; +import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE; + +import android.annotation.DimenRes; +import android.content.res.ColorStateList; +import android.content.res.Resources; +import android.graphics.drawable.Animatable; +import android.graphics.drawable.Drawable; +import android.hardware.SensorPrivacyManager; +import android.os.Bundle; +import android.provider.Settings; +import android.util.Log; +import android.view.View; +import android.view.WindowManager; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.TextView; + +import com.android.systemui.R; +import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController; +import com.android.systemui.tv.TvBottomSheetActivity; +import com.android.systemui.util.settings.GlobalSettings; + +import javax.inject.Inject; + +/** + * Bottom sheet that is shown when the camera/mic sensors privacy state changed + * by the global software toggle or physical privacy switch. + */ +public class TvSensorPrivacyChangedActivity extends TvBottomSheetActivity { + + private static final String TAG = TvSensorPrivacyChangedActivity.class.getSimpleName(); + + private static final int ALL_SENSORS = Integer.MAX_VALUE; + + private int mSensor = -1; + private int mToggleType = -1; + + private final GlobalSettings mGlobalSettings; + private final IndividualSensorPrivacyController mSensorPrivacyController; + private IndividualSensorPrivacyController.Callback mSensorPrivacyCallback; + private TextView mTitle; + private TextView mContent; + private ImageView mIcon; + private ImageView mSecondIcon; + private Button mPositiveButton; + private Button mCancelButton; + + @Inject + public TvSensorPrivacyChangedActivity( + IndividualSensorPrivacyController individualSensorPrivacyController, + GlobalSettings globalSettings) { + mSensorPrivacyController = individualSensorPrivacyController; + mGlobalSettings = globalSettings; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + getWindow().addSystemFlags( + WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); + + boolean allSensors = getIntent().getBooleanExtra(SensorPrivacyManager.EXTRA_ALL_SENSORS, + false); + if (allSensors) { + mSensor = ALL_SENSORS; + } else { + mSensor = getIntent().getIntExtra(SensorPrivacyManager.EXTRA_SENSOR, -1); + } + + mToggleType = getIntent().getIntExtra(SensorPrivacyManager.EXTRA_TOGGLE_TYPE, -1); + + if (mSensor == -1 || mToggleType == -1) { + Log.v(TAG, "Invalid extras"); + finish(); + return; + } + + // Do not show for software toggles + if (mToggleType == SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE) { + finish(); + return; + } + + mSensorPrivacyCallback = (sensor, blocked) -> { + updateUI(); + }; + + initUI(); + } + + private void initUI() { + mTitle = findViewById(R.id.bottom_sheet_title); + mContent = findViewById(R.id.bottom_sheet_body); + mIcon = findViewById(R.id.bottom_sheet_icon); + // mic icon if both icons are shown + mSecondIcon = findViewById(R.id.bottom_sheet_second_icon); + mPositiveButton = findViewById(R.id.bottom_sheet_positive_button); + mCancelButton = findViewById(R.id.bottom_sheet_negative_button); + + mCancelButton.setText(android.R.string.cancel); + mCancelButton.setOnClickListener(v -> finish()); + + updateUI(); + } + + private void updateUI() { + final Resources resources = getResources(); + setIconTint(resources.getBoolean(R.bool.config_unblockHwSensorIconEnableTint)); + setIconSize(R.dimen.unblock_hw_sensor_icon_width, R.dimen.unblock_hw_sensor_icon_height); + + switch (mSensor) { + case CAMERA: + updateUiForCameraUpdate( + mSensorPrivacyController.isSensorBlockedByHardwareToggle(CAMERA)); + break; + case MICROPHONE: + default: + updateUiForMicUpdate( + mSensorPrivacyController.isSensorBlockedByHardwareToggle(MICROPHONE)); + break; + } + + // Start animation if drawable is animated + Drawable iconDrawable = mIcon.getDrawable(); + if (iconDrawable instanceof Animatable) { + ((Animatable) iconDrawable).start(); + } + + mPositiveButton.setVisibility(View.GONE); + mCancelButton.setText(android.R.string.ok); + } + + private void updateUiForMicUpdate(boolean blocked) { + if (blocked) { + mTitle.setText(R.string.sensor_privacy_mic_turned_off_dialog_title); + if (isExplicitUserInteractionAudioBypassAllowed()) { + mContent.setText(R.string.sensor_privacy_mic_blocked_with_exception_dialog_content); + } else { + mContent.setText(R.string.sensor_privacy_mic_blocked_no_exception_dialog_content); + } + mIcon.setImageResource(R.drawable.unblock_hw_sensor_microphone); + mSecondIcon.setVisibility(View.GONE); + } else { + mTitle.setText(R.string.sensor_privacy_mic_turned_on_dialog_title); + mContent.setText(R.string.sensor_privacy_mic_unblocked_dialog_content); + mIcon.setImageResource(com.android.internal.R.drawable.ic_mic_allowed); + mSecondIcon.setVisibility(View.GONE); + } + } + + private void updateUiForCameraUpdate(boolean blocked) { + if (blocked) { + mTitle.setText(R.string.sensor_privacy_camera_turned_off_dialog_title); + mContent.setText(R.string.sensor_privacy_camera_blocked_dialog_content); + mIcon.setImageResource(R.drawable.unblock_hw_sensor_camera); + mSecondIcon.setVisibility(View.GONE); + } else { + mTitle.setText(R.string.sensor_privacy_camera_turned_on_dialog_title); + mContent.setText(R.string.sensor_privacy_camera_unblocked_dialog_content); + mIcon.setImageResource(com.android.internal.R.drawable.ic_camera_allowed); + mSecondIcon.setVisibility(View.GONE); + } + } + + private void setIconTint(boolean enableTint) { + final Resources resources = getResources(); + + if (enableTint) { + final ColorStateList iconTint = resources.getColorStateList( + R.color.bottom_sheet_icon_color, getTheme()); + mIcon.setImageTintList(iconTint); + mSecondIcon.setImageTintList(iconTint); + } else { + mIcon.setImageTintList(null); + mSecondIcon.setImageTintList(null); + } + + mIcon.invalidate(); + mSecondIcon.invalidate(); + } + + private void setIconSize(@DimenRes int widthRes, @DimenRes int heightRes) { + final Resources resources = getResources(); + final int iconWidth = resources.getDimensionPixelSize(widthRes); + final int iconHeight = resources.getDimensionPixelSize(heightRes); + + mIcon.getLayoutParams().width = iconWidth; + mIcon.getLayoutParams().height = iconHeight; + mIcon.invalidate(); + + mSecondIcon.getLayoutParams().width = iconWidth; + mSecondIcon.getLayoutParams().height = iconHeight; + mSecondIcon.invalidate(); + } + + private boolean isExplicitUserInteractionAudioBypassAllowed() { + return mGlobalSettings.getInt( + Settings.Global.RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO_ENABLED, 1) == 1; + } + + @Override + public void onResume() { + super.onResume(); + updateUI(); + mSensorPrivacyController.addCallback(mSensorPrivacyCallback); + } + + @Override + public void onPause() { + mSensorPrivacyController.removeCallback(mSensorPrivacyCallback); + super.onPause(); + } + +} diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvUnblockSensorActivity.java b/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvUnblockSensorActivity.java index d543eb2d647ac..1b9657f3dca76 100644 --- a/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvUnblockSensorActivity.java +++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvUnblockSensorActivity.java @@ -16,17 +16,23 @@ package com.android.systemui.sensorprivacy.television; +import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY; import static android.hardware.SensorPrivacyManager.Sensors.CAMERA; import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE; import static android.hardware.SensorPrivacyManager.Sources.OTHER; import android.annotation.DimenRes; +import android.app.AppOpsManager; +import android.app.role.RoleManager; +import android.content.Intent; +import android.content.pm.ActivityInfo; import android.content.res.ColorStateList; import android.content.res.Resources; import android.graphics.drawable.Animatable; import android.graphics.drawable.Drawable; import android.hardware.SensorPrivacyManager; import android.os.Bundle; +import android.os.UserHandle; import android.util.Log; import android.view.View; import android.view.WindowManager; @@ -39,6 +45,8 @@ import com.android.systemui.R; import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController; import com.android.systemui.tv.TvBottomSheetActivity; +import java.util.List; + import javax.inject.Inject; /** @@ -57,6 +65,8 @@ public class TvUnblockSensorActivity extends TvBottomSheetActivity { private int mSensor = -1; + private final AppOpsManager mAppOpsManager; + private final RoleManager mRoleManager; private final IndividualSensorPrivacyController mSensorPrivacyController; private IndividualSensorPrivacyController.Callback mSensorPrivacyCallback; private TextView mTitle; @@ -68,8 +78,11 @@ public class TvUnblockSensorActivity extends TvBottomSheetActivity { @Inject public TvUnblockSensorActivity( - IndividualSensorPrivacyController individualSensorPrivacyController) { + IndividualSensorPrivacyController individualSensorPrivacyController, + AppOpsManager appOpsManager, RoleManager roleManager) { mSensorPrivacyController = individualSensorPrivacyController; + mAppOpsManager = appOpsManager; + mRoleManager = roleManager; } @Override @@ -120,6 +133,10 @@ public class TvUnblockSensorActivity extends TvBottomSheetActivity { toastMsgResId = R.string.sensor_privacy_mic_camera_unblocked_toast_content; break; } + showToastAndFinish(toastMsgResId); + } + + private void showToastAndFinish(int toastMsgResId) { Toast.makeText(this, toastMsgResId, Toast.LENGTH_SHORT).show(); finish(); } @@ -149,7 +166,9 @@ public class TvUnblockSensorActivity extends TvBottomSheetActivity { } private void updateUI() { - if (isBlockedByHardwareToggle()) { + if (isHTTAccessDisabled()) { + updateUiForHTT(); + } else if (isBlockedByHardwareToggle()) { updateUiForHardwareToggle(); } else { updateUiForSoftwareToggle(); @@ -208,20 +227,20 @@ public class TvUnblockSensorActivity extends TvBottomSheetActivity { switch (mSensor) { case MICROPHONE: - mTitle.setText(R.string.sensor_privacy_start_use_mic_dialog_title); + mTitle.setText(R.string.sensor_privacy_start_use_mic_blocked_dialog_title); mContent.setText(R.string.sensor_privacy_start_use_mic_dialog_content); mIcon.setImageResource(com.android.internal.R.drawable.perm_group_microphone); mSecondIcon.setVisibility(View.GONE); break; case CAMERA: - mTitle.setText(R.string.sensor_privacy_start_use_camera_dialog_title); + mTitle.setText(R.string.sensor_privacy_start_use_camera_blocked_dialog_title); mContent.setText(R.string.sensor_privacy_start_use_camera_dialog_content); mIcon.setImageResource(com.android.internal.R.drawable.perm_group_camera); mSecondIcon.setVisibility(View.GONE); break; case ALL_SENSORS: default: - mTitle.setText(R.string.sensor_privacy_start_use_mic_camera_dialog_title); + mTitle.setText(R.string.sensor_privacy_start_use_mic_camera_blocked_dialog_title); mContent.setText(R.string.sensor_privacy_start_use_mic_camera_dialog_content); mIcon.setImageResource(com.android.internal.R.drawable.perm_group_camera); mSecondIcon.setImageResource( @@ -241,6 +260,29 @@ public class TvUnblockSensorActivity extends TvBottomSheetActivity { }); } + private void updateUiForHTT() { + setIconTint(true); + setIconSize(R.dimen.bottom_sheet_icon_size, R.dimen.bottom_sheet_icon_size); + + mTitle.setText(R.string.sensor_privacy_start_use_mic_blocked_dialog_title); + mContent.setText(R.string.sensor_privacy_htt_blocked_dialog_content); + mIcon.setImageResource(com.android.internal.R.drawable.perm_group_microphone); + mSecondIcon.setVisibility(View.GONE); + + mPositiveButton.setText(R.string.sensor_privacy_dialog_open_settings); + mPositiveButton.setOnClickListener(v -> { + Intent openPrivacySettings = new Intent(ACTION_MANAGE_MICROPHONE_PRIVACY); + ActivityInfo activityInfo = openPrivacySettings.resolveActivityInfo(getPackageManager(), + MATCH_SYSTEM_ONLY); + if (activityInfo == null) { + showToastAndFinish(com.android.internal.R.string.noApplications); + } else { + startActivity(openPrivacySettings); + finish(); + } + }); + } + private void setIconTint(boolean enableTint) { final Resources resources = getResources(); @@ -272,6 +314,18 @@ public class TvUnblockSensorActivity extends TvBottomSheetActivity { mSecondIcon.invalidate(); } + private boolean isHTTAccessDisabled() { + String pkg = getIntent().getStringExtra(Intent.EXTRA_PACKAGE_NAME); + List assistantPkgs = mRoleManager.getRoleHolders(RoleManager.ROLE_ASSISTANT); + if (!assistantPkgs.contains(pkg)) { + return false; + } + + return (mAppOpsManager.checkOpNoThrow( + AppOpsManager.OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, UserHandle.myUserId(), + pkg) != AppOpsManager.MODE_ALLOWED); + } + @Override public void onResume() { super.onResume(); diff --git a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java index 61c21e6b4d703..ab35dc86b9617 100644 --- a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java +++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java @@ -35,6 +35,7 @@ import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.hardware.SensorPrivacyManager.EXTRA_ALL_SENSORS; import static android.hardware.SensorPrivacyManager.EXTRA_SENSOR; +import static android.hardware.SensorPrivacyManager.EXTRA_TOGGLE_TYPE; import static android.hardware.SensorPrivacyManager.Sensors.CAMERA; import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE; import static android.hardware.SensorPrivacyManager.Sources.DIALOG; @@ -664,6 +665,29 @@ public final class SensorPrivacyService extends SystemService { .build()); } + private void showSensorStateChangedActivity(@SensorPrivacyManager.Sensors.Sensor int sensor, + @SensorPrivacyManager.ToggleType int toggleType) { + String activityName = mContext.getResources().getString( + R.string.config_sensorStateChangedActivity); + if (TextUtils.isEmpty(activityName)) { + return; + } + + Intent dialogIntent = new Intent(); + dialogIntent.setComponent( + ComponentName.unflattenFromString(activityName)); + + ActivityOptions options = ActivityOptions.makeBasic(); + options.setTaskOverlay(true, true); + + dialogIntent.addFlags( + FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS | FLAG_ACTIVITY_NO_USER_ACTION); + + dialogIntent.putExtra(EXTRA_SENSOR, sensor); + dialogIntent.putExtra(EXTRA_TOGGLE_TYPE, toggleType); + mContext.startActivityAsUser(dialogIntent, options.toBundle(), UserHandle.SYSTEM); + } + private boolean isTelevision(Context context) { int uiMode = context.getResources().getConfiguration().uiMode; return (uiMode & Configuration.UI_MODE_TYPE_MASK) @@ -1378,6 +1402,8 @@ public final class SensorPrivacyService extends SystemService { mToggleSensorListeners.finishBroadcast(); } } + + mSensorPrivacyServiceImpl.showSensorStateChangedActivity(sensor, toggleType); } public void removeSuppressPackageReminderToken(Pair key,