From a270fc3f96e80b6e1d92df40bd09119ce8ebcad3 Mon Sep 17 00:00:00 2001 From: Evan Severson Date: Mon, 29 Mar 2021 15:47:17 -0700 Subject: [PATCH] Update mic/cam toggles to the latest UI mocks This touches the sensor use dialog and the notification. Also we shouldn't leak listeners every time the dialg is made. TODO: Detect when both camera and mic are opened at the same time TODO: Settings page Test: Manually open all UI available Bug: 183985427 Change-Id: Ie743f402835d8fd3e644a0374f03062f0a61c31a --- .../hardware/SensorPrivacyManager.java | 7 + core/res/res/drawable/ic_camera_allowed.xml | 25 ++++ core/res/res/drawable/ic_camera_blocked.xml | 42 +++--- core/res/res/drawable/ic_mic_allowed.xml | 25 ++++ core/res/res/drawable/ic_mic_blocked.xml | 42 +++--- core/res/res/values/strings.xml | 10 +- core/res/res/values/symbols.xml | 7 +- .../res/layout/sensor_use_started_title.xml | 53 ++++++++ packages/SystemUI/res/values/strings.xml | 21 ++- .../dagger/DefaultActivityBinder.java | 7 + .../systemui/qs/tiles/CameraToggleTile.java | 10 +- .../qs/tiles/MicrophoneToggleTile.java | 10 +- .../qs/tiles/SensorPrivacyToggleTile.java | 20 ++- .../sensorprivacy/SensorUseStartedActivity.kt | 126 +++++++++++++----- .../IndividualSensorPrivacyController.java | 2 + ...IndividualSensorPrivacyControllerImpl.java | 5 + .../android/server/SensorPrivacyService.java | 14 +- 17 files changed, 321 insertions(+), 105 deletions(-) create mode 100644 core/res/res/drawable/ic_camera_allowed.xml create mode 100644 core/res/res/drawable/ic_mic_allowed.xml create mode 100644 packages/SystemUI/res/layout/sensor_use_started_title.xml diff --git a/core/java/android/hardware/SensorPrivacyManager.java b/core/java/android/hardware/SensorPrivacyManager.java index e03c1f48773ae..e8c426f489f39 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_SENSOR = SensorPrivacyManager.class.getName() + ".extra.sensor"; + /** + * An extra indicating if all sensors are affected + * @hide + */ + public static final String EXTRA_ALL_SENSORS = SensorPrivacyManager.class.getName() + + ".extra.all_sensors"; + /** * Individual sensors not listed in {@link Sensors} * @hide diff --git a/core/res/res/drawable/ic_camera_allowed.xml b/core/res/res/drawable/ic_camera_allowed.xml new file mode 100644 index 0000000000000..990bc44d3ec68 --- /dev/null +++ b/core/res/res/drawable/ic_camera_allowed.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/core/res/res/drawable/ic_camera_blocked.xml b/core/res/res/drawable/ic_camera_blocked.xml index 0161bcbd19370..775974e4808a9 100644 --- a/core/res/res/drawable/ic_camera_blocked.xml +++ b/core/res/res/drawable/ic_camera_blocked.xml @@ -1,29 +1,25 @@ + + Copyright (C) 2021 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. +--> - - + android:viewportHeight="24" + android:tint="?attr/colorControlNormal"> + diff --git a/core/res/res/drawable/ic_mic_allowed.xml b/core/res/res/drawable/ic_mic_allowed.xml new file mode 100644 index 0000000000000..9dbae91cdc26b --- /dev/null +++ b/core/res/res/drawable/ic_mic_allowed.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/core/res/res/drawable/ic_mic_blocked.xml b/core/res/res/drawable/ic_mic_blocked.xml index 0ce7a581a7e5d..21fc7aa8b0259 100644 --- a/core/res/res/drawable/ic_mic_blocked.xml +++ b/core/res/res/drawable/ic_mic_blocked.xml @@ -1,29 +1,25 @@ + + Copyright (C) 2021 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. +--> - - + android:viewportHeight="24" + android:tint="?attr/colorControlNormal"> + diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index b834a069c5b7c..7a4695dd9adbe 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -5930,12 +5930,14 @@ ul. Dismiss - - To continue, <b>‎‏‎‎‏‏‎%s</b> needs access to your device microphone. + + Unblock device microphone + + Unblock device camera - To continue, <b>%s</b> needs access to your device’s camera. + For <b>%s</b> and all apps and services - Turn on + Unblock Sensor Privacy diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 8621147137dd1..b199595739b16 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -4207,8 +4207,11 @@ - - + + + + + diff --git a/packages/SystemUI/res/layout/sensor_use_started_title.xml b/packages/SystemUI/res/layout/sensor_use_started_title.xml new file mode 100644 index 0000000000000..3e61262926db5 --- /dev/null +++ b/packages/SystemUI/res/layout/sensor_use_started_title.xml @@ -0,0 +1,53 @@ + + + + + + + + + + diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 3ca885a2c6b7c..958c2d257906b 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -826,9 +826,13 @@ Location Off - Block Camera + Camera access - Mute Microphone + Mic access + + Available + + Blocked Media device @@ -975,10 +979,19 @@ Stop + + Unblock device microphone? + + Unblock device camera? + + Unblock device camera and microphone? + - To continue, <b>%s</b> needs access to your device microphone. + This unblocks access for all apps and services allowed to use your microphone. - To continue, <b>%s</b> needs access to your device’s camera. + This unblocks access for all apps and services allowed to use your camera. + + This unblocks access for all apps and services allowed to use your camera or microphone. Device diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java index 242c6afebc3e7..2dbf30fdd2899 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java @@ -24,6 +24,7 @@ import com.android.systemui.people.PeopleSpaceActivity; import com.android.systemui.people.widget.LaunchConversationActivity; import com.android.systemui.screenrecord.ScreenRecordDialog; import com.android.systemui.screenshot.LongScreenshotActivity; +import com.android.systemui.sensorprivacy.SensorUseStartedActivity; import com.android.systemui.settings.brightness.BrightnessDialog; import com.android.systemui.statusbar.tv.notifications.TvNotificationPanelActivity; import com.android.systemui.tuner.TunerActivity; @@ -113,4 +114,10 @@ public abstract class DefaultActivityBinder { @IntoMap @ClassKey(LaunchConversationActivity.class) public abstract Activity bindLaunchConversationActivity(LaunchConversationActivity activity); + + /** Inject into SensorUseStartedActivity. */ + @Binds + @IntoMap + @ClassKey(SensorUseStartedActivity.class) + public abstract Activity bindSensorUseStartedActivity(SensorUseStartedActivity activity); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java index 0d73a5a977064..e05558598e00b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java @@ -66,12 +66,16 @@ public class CameraToggleTile extends SensorPrivacyToggleTile { return getHost().getContext().getPackageManager().hasSystemFeature(FEATURE_CAMERA_TOGGLE) && whitelistIpcs(() -> DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, "camera_toggle_enabled", - false)); + true)); } @Override - public @DrawableRes int getIconRes() { - return com.android.internal.R.drawable.ic_camera_blocked; + public @DrawableRes int getIconRes(boolean isBlocked) { + if (isBlocked) { + return com.android.internal.R.drawable.ic_camera_blocked; + } else { + return com.android.internal.R.drawable.ic_camera_allowed; + } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java index b8d879226f558..9c01bb9cc1fdf 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java @@ -67,12 +67,16 @@ public class MicrophoneToggleTile extends SensorPrivacyToggleTile { .hasSystemFeature(FEATURE_MICROPHONE_TOGGLE) && whitelistIpcs(() -> DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, "mic_toggle_enabled", - false)); + true)); } @Override - public @DrawableRes int getIconRes() { - return com.android.internal.R.drawable.ic_mic_blocked; + public @DrawableRes int getIconRes(boolean isBlocked) { + if (isBlocked) { + return com.android.internal.R.drawable.ic_mic_blocked; + } else { + return com.android.internal.R.drawable.ic_mic_allowed; + } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java index a492330c1796f..5d2d6f8845a48 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java @@ -26,10 +26,12 @@ import android.widget.Switch; import androidx.annotation.DrawableRes; import com.android.internal.logging.MetricsLogger; +import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.FalsingManager; +import com.android.systemui.plugins.qs.DetailAdapter; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; @@ -55,7 +57,7 @@ public abstract class SensorPrivacyToggleTile extends QSTileImplThe dialog is started for the user the app is running for which might be a secondary users. */ -class SensorUseStartedActivity : AlertActivity(), DialogInterface.OnClickListener { +class SensorUseStartedActivity @Inject constructor( + private val sensorPrivacyController: IndividualSensorPrivacyController, + private val keyguardManager: KeyguardManager, + private val keyguardUpdateMonitor: KeyguardUpdateMonitor +) : AlertActivity(), DialogInterface.OnClickListener { companion object { private val LOG_TAG = SensorUseStartedActivity::class.java.simpleName private const val SUPPRESS_REMINDERS_REMOVAL_DELAY_MILLIS = 2000L + + private const val CAMERA = SensorPrivacyManager.Sensors.CAMERA + private const val MICROPHONE = SensorPrivacyManager.Sensors.MICROPHONE + private const val ALL_SENSORS = Integer.MAX_VALUE } private var sensor = -1 private lateinit var sensorUsePackageName: String private var unsuppressImmediately = false - private lateinit var sensorPrivacyManager: SensorPrivacyManager - private lateinit var appOpsManager: AppOpsManager - private lateinit var keyguardManager: KeyguardManager + private lateinit var sensorPrivacyListener: IndividualSensorPrivacyController.Callback override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -64,35 +76,81 @@ class SensorUseStartedActivity : AlertActivity(), DialogInterface.OnClickListene setFinishOnTouchOutside(false) setResult(RESULT_CANCELED) - sensorPrivacyManager = getSystemService(SensorPrivacyManager::class.java)!! - appOpsManager = getSystemService(AppOpsManager::class.java)!! - keyguardManager = getSystemService(KeyguardManager::class.java)!! sensorUsePackageName = intent.getStringExtra(EXTRA_PACKAGE_NAME) ?: return - sensor = intent.getIntExtra(EXTRA_SENSOR, -1).also { - if (it == -1) { + + if (intent.getBooleanExtra(EXTRA_ALL_SENSORS, false)) { + sensor = ALL_SENSORS + sensorPrivacyListener = + IndividualSensorPrivacyController.Callback { _, _ -> + if (!sensorPrivacyController.isSensorBlocked(MICROPHONE) && + !sensorPrivacyController.isSensorBlocked(CAMERA)) { + dismiss() + } + } + + sensorPrivacyController.addCallback(sensorPrivacyListener) + if (!sensorPrivacyController.isSensorBlocked(MICROPHONE) && + !sensorPrivacyController.isSensorBlocked(CAMERA)) { + finish() + return + } + } else { + sensor = intent.getIntExtra(EXTRA_SENSOR, -1).also { + if (it == -1) { + finish() + return + } + } + sensorPrivacyListener = + IndividualSensorPrivacyController.Callback { + whichSensor: Int, isBlocked: Boolean -> + if (whichSensor == sensor && !isBlocked) { + dismiss() + } + } + sensorPrivacyController.addCallback(sensorPrivacyListener) + + if (!sensorPrivacyController.isSensorBlocked(sensor)) { finish() return } } - sensorPrivacyManager.addSensorPrivacyListener(sensor) { isBlocked -> - if (!isBlocked) { - dismiss() - } - } - if (!sensorPrivacyManager.isSensorPrivacyEnabled(sensor)) { - finish() - return - } - mAlertParams.apply { try { + mCustomTitleView = mInflater.inflate(R.layout.sensor_use_started_title, null) + mCustomTitleView.findViewById(R.id.sensor_use_started_title_message)!! + .setText(when (sensor) { + MICROPHONE -> + R.string.sensor_privacy_start_use_mic_dialog_title + CAMERA -> + R.string.sensor_privacy_start_use_camera_dialog_title + ALL_SENSORS -> + R.string.sensor_privacy_start_use_mic_camera_dialog_title + else -> Resources.ID_NULL + }) + + mCustomTitleView.findViewById(R.id.sensor_use_microphone_icon)!! + .visibility = if (sensor == MICROPHONE || sensor == ALL_SENSORS) { + VISIBLE + } else { + GONE + } + mCustomTitleView.findViewById(R.id.sensor_use_camera_icon)!! + .visibility = if (sensor == CAMERA || sensor == ALL_SENSORS) { + VISIBLE + } else { + GONE + } + mMessage = Html.fromHtml(getString(when (sensor) { MICROPHONE -> R.string.sensor_privacy_start_use_mic_dialog_content CAMERA -> R.string.sensor_privacy_start_use_camera_dialog_content + ALL_SENSORS -> + R.string.sensor_privacy_start_use_mic_camera_dialog_content else -> Resources.ID_NULL }, packageManager.getApplicationInfo(sensorUsePackageName, 0) .loadLabel(packageManager)), 0) @@ -101,13 +159,6 @@ class SensorUseStartedActivity : AlertActivity(), DialogInterface.OnClickListene return } - mIconId = when (sensor) { - MICROPHONE -> - com.android.internal.R.drawable.perm_group_microphone - CAMERA -> com.android.internal.R.drawable.perm_group_camera - else -> Resources.ID_NULL - } - mPositiveButtonText = getString( com.android.internal.R.string.sensor_privacy_start_use_dialog_turn_on_button) mNegativeButtonText = getString(android.R.string.cancel) @@ -121,14 +172,14 @@ class SensorUseStartedActivity : AlertActivity(), DialogInterface.OnClickListene override fun onStart() { super.onStart() - sensorPrivacyManager.suppressSensorPrivacyReminders(sensorUsePackageName, true) + sensorPrivacyController.suppressSensorPrivacyReminders(sensorUsePackageName, true) unsuppressImmediately = false } override fun onClick(dialog: DialogInterface?, which: Int) { when (which) { BUTTON_POSITIVE -> { - if (keyguardManager.isDeviceLocked) { + if (keyguardUpdateMonitor.getUserHasTrust(userId)) { keyguardManager .requestDismissKeyguard(this, object : KeyguardDismissCallback() { override fun onDismissError() { @@ -152,25 +203,30 @@ class SensorUseStartedActivity : AlertActivity(), DialogInterface.OnClickListene } override fun onStop() { - super.onDestroy() + super.onStop() if (unsuppressImmediately) { - sensorPrivacyManager + sensorPrivacyController .suppressSensorPrivacyReminders(sensorUsePackageName, false) } else { Handler(mainLooper).postDelayed({ - sensorPrivacyManager + sensorPrivacyController .suppressSensorPrivacyReminders(sensorUsePackageName, false) }, SUPPRESS_REMINDERS_REMOVAL_DELAY_MILLIS) } } + override fun onDestroy() { + super.onDestroy() + sensorPrivacyController.removeCallback(sensorPrivacyListener) + } + override fun onBackPressed() { // do not allow backing out } private fun disableSensorPrivacy() { - sensorPrivacyManager.setSensorPrivacyForProfileGroup(sensor, false) + sensorPrivacyController.setSensorBlocked(sensor, false) unsuppressImmediately = true setResult(RESULT_OK) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyController.java index 7f935d28285f3..4e6db40d78edb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyController.java @@ -26,6 +26,8 @@ public interface IndividualSensorPrivacyController extends void setSensorBlocked(@Sensor int sensor, boolean blocked); + void suppressSensorPrivacyReminders(String packageName, boolean suppress); + interface Callback { void onSensorBlockedChanged(@Sensor int sensor, boolean blocked); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java index 295df05797eab..beb4b44323a5c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java @@ -61,6 +61,11 @@ public class IndividualSensorPrivacyControllerImpl implements IndividualSensorPr mSensorPrivacyManager.setSensorPrivacyForProfileGroup(sensor, blocked); } + @Override + public void suppressSensorPrivacyReminders(String packageName, boolean suppress) { + mSensorPrivacyManager.suppressSensorPrivacyReminders(packageName, suppress); + } + @Override public void addCallback(@NonNull Callback listener) { mCallbacks.add(listener); diff --git a/services/core/java/com/android/server/SensorPrivacyService.java b/services/core/java/com/android/server/SensorPrivacyService.java index 18d47c6fb7b3d..df6ab5da70d49 100644 --- a/services/core/java/com/android/server/SensorPrivacyService.java +++ b/services/core/java/com/android/server/SensorPrivacyService.java @@ -63,6 +63,7 @@ import android.os.ResultReceiver; import android.os.ShellCallback; import android.os.ShellCommand; import android.os.UserHandle; +import android.provider.Settings; import android.service.SensorPrivacyIndividualEnabledSensorProto; import android.service.SensorPrivacyServiceDumpProto; import android.service.SensorPrivacyUserProto; @@ -368,10 +369,10 @@ public final class SensorPrivacyService extends SystemService { if (sensor == MICROPHONE) { iconRes = R.drawable.ic_mic_blocked; - messageRes = R.string.sensor_privacy_start_use_mic_notification_content; + messageRes = R.string.sensor_privacy_start_use_mic_notification_content_title; } else { iconRes = R.drawable.ic_camera_blocked; - messageRes = R.string.sensor_privacy_start_use_camera_notification_content; + messageRes = R.string.sensor_privacy_start_use_camera_notification_content_title; } NotificationManager notificationManager = @@ -390,10 +391,11 @@ public final class SensorPrivacyService extends SystemService { Icon icon = Icon.createWithResource(getUiContext().getResources(), iconRes); notificationManager.notify(sensor, new Notification.Builder(mContext, SENSOR_PRIVACY_CHANNEL_ID) - .setContentTitle(Html.fromHtml(getUiContext().getString(messageRes, + .setContentTitle(getUiContext().getString(messageRes)) + .setContentText(Html.fromHtml(getUiContext().getString( + R.string.sensor_privacy_start_use_notification_content_text, packageLabel),0)) .setSmallIcon(icon) - .setLargeIcon(icon) .addAction(new Notification.Action.Builder(icon, getUiContext().getString( R.string.sensor_privacy_start_use_dialog_turn_on_button), @@ -405,6 +407,10 @@ public final class SensorPrivacyService extends SystemService { PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT)) .build()) + .setContentIntent(PendingIntent.getActivity(mContext, sensor, + new Intent(Settings.ACTION_PRIVACY_SETTINGS), + PendingIntent.FLAG_IMMUTABLE + | PendingIntent.FLAG_UPDATE_CURRENT)) .build()); }