diff --git a/core/java/android/hardware/SensorPrivacyManager.java b/core/java/android/hardware/SensorPrivacyManager.java index d30848f2d1d3d..85e7d77d93cd6 100644 --- a/core/java/android/hardware/SensorPrivacyManager.java +++ b/core/java/android/hardware/SensorPrivacyManager.java @@ -73,6 +73,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 90e9c098bf292..c1ef2f42ab230 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -5941,12 +5941,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 dbfb0307e96e4..8c8d99ec239ec 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -4202,8 +4202,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 4a29578cb766f..269fdc53853bc 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -832,9 +832,13 @@ Location Off - Block Camera + Camera access - Mute Microphone + Mic access + + Available + + Blocked Media device @@ -981,10 +985,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 5b986b6fece9a..e05558598e00b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java @@ -70,8 +70,12 @@ public class CameraToggleTile extends SensorPrivacyToggleTile { } @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 42bd77b354bb7..9c01bb9cc1fdf 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java @@ -71,8 +71,12 @@ public class MicrophoneToggleTile extends SensorPrivacyToggleTile { } @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()); }