1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.sensorprivacy 18 19 import android.app.Activity 20 import android.app.AlertDialog 21 import android.content.DialogInterface 22 import android.content.DialogInterface.BUTTON_NEGATIVE 23 import android.content.DialogInterface.BUTTON_POSITIVE 24 import android.content.Intent 25 import android.content.Intent.EXTRA_PACKAGE_NAME 26 import android.hardware.SensorPrivacyManager 27 import android.hardware.SensorPrivacyManager.EXTRA_ALL_SENSORS 28 import android.hardware.SensorPrivacyManager.EXTRA_SENSOR 29 import android.hardware.SensorPrivacyManager.Sources.DIALOG 30 import android.os.Bundle 31 import android.os.Handler 32 import android.window.OnBackInvokedDispatcher 33 import androidx.annotation.OpenForTesting 34 import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION 35 import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION__ACTION__CANCEL 36 import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION__ACTION__ENABLE 37 import com.android.internal.util.FrameworkStatsLog.write 38 import com.android.systemui.dagger.qualifiers.Background 39 import com.android.systemui.statusbar.phone.KeyguardDismissUtil 40 import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController 41 import com.android.systemui.statusbar.policy.KeyguardStateController 42 import javax.inject.Inject 43 44 /** 45 * Dialog to be shown on top of apps that are attempting to use a sensor (e.g. microphone) which is 46 * currently in "sensor privacy mode", aka. muted. 47 * 48 * <p>The dialog is started for the user the app is running for which might be a secondary users. 49 */ 50 @OpenForTesting 51 open class SensorUseStartedActivity @Inject constructor( 52 private val sensorPrivacyController: IndividualSensorPrivacyController, 53 private val keyguardStateController: KeyguardStateController, 54 private val keyguardDismissUtil: KeyguardDismissUtil, 55 @Background private val bgHandler: Handler 56 ) : Activity(), DialogInterface.OnClickListener, DialogInterface.OnDismissListener { 57 58 companion object { 59 private val LOG_TAG = SensorUseStartedActivity::class.java.simpleName 60 61 private const val SUPPRESS_REMINDERS_REMOVAL_DELAY_MILLIS = 2000L 62 private const val UNLOCK_DELAY_MILLIS = 200L 63 64 internal const val CAMERA = SensorPrivacyManager.Sensors.CAMERA 65 internal const val MICROPHONE = SensorPrivacyManager.Sensors.MICROPHONE 66 internal const val ALL_SENSORS = Integer.MAX_VALUE 67 } 68 69 private var sensor = -1 70 private lateinit var sensorUsePackageName: String 71 private var unsuppressImmediately = false 72 73 private var sensorPrivacyListener: IndividualSensorPrivacyController.Callback? = null 74 75 private var mDialog: AlertDialog? = null 76 private val mBackCallback = this::onBackInvoked 77 78 override fun onCreate(savedInstanceState: Bundle?) { 79 super.onCreate(savedInstanceState) 80 81 setShowWhenLocked(true) 82 83 setFinishOnTouchOutside(false) 84 85 setResult(RESULT_CANCELED) 86 87 sensorUsePackageName = intent.getStringExtra(EXTRA_PACKAGE_NAME) ?: return 88 89 if (intent.getBooleanExtra(EXTRA_ALL_SENSORS, false)) { 90 sensor = ALL_SENSORS 91 val callback = IndividualSensorPrivacyController.Callback { _, _ -> 92 if (!sensorPrivacyController.isSensorBlocked(MICROPHONE) && 93 !sensorPrivacyController.isSensorBlocked(CAMERA)) { 94 finish() 95 } 96 } 97 sensorPrivacyListener = callback 98 sensorPrivacyController.addCallback(callback) 99 if (!sensorPrivacyController.isSensorBlocked(MICROPHONE) && 100 !sensorPrivacyController.isSensorBlocked(CAMERA)) { 101 finish() 102 return 103 } 104 } else { 105 sensor = intent.getIntExtra(EXTRA_SENSOR, -1).also { 106 if (it == -1) { 107 finish() 108 return 109 } 110 } 111 val callback = IndividualSensorPrivacyController.Callback { 112 whichSensor: Int, isBlocked: Boolean -> 113 if (whichSensor == sensor && !isBlocked) { 114 finish() 115 } 116 } 117 sensorPrivacyListener = callback 118 sensorPrivacyController.addCallback(callback) 119 120 if (!sensorPrivacyController.isSensorBlocked(sensor)) { 121 finish() 122 return 123 } 124 } 125 126 mDialog = SensorUseDialog(this, sensor, this, this) 127 mDialog!!.show() 128 129 onBackInvokedDispatcher.registerOnBackInvokedCallback( 130 OnBackInvokedDispatcher.PRIORITY_DEFAULT, 131 mBackCallback) 132 } 133 134 override fun onStart() { 135 super.onStart() 136 137 setSuppressed(true) 138 unsuppressImmediately = false 139 } 140 141 override fun onClick(dialog: DialogInterface?, which: Int) { 142 when (which) { 143 BUTTON_POSITIVE -> { 144 if (sensorPrivacyController.requiresAuthentication() && 145 keyguardStateController.isMethodSecure && 146 keyguardStateController.isShowing) { 147 keyguardDismissUtil.executeWhenUnlocked({ 148 bgHandler.postDelayed({ 149 disableSensorPrivacy() 150 write(PRIVACY_TOGGLE_DIALOG_INTERACTION, 151 PRIVACY_TOGGLE_DIALOG_INTERACTION__ACTION__ENABLE, 152 sensorUsePackageName) 153 }, UNLOCK_DELAY_MILLIS) 154 155 false 156 }, false, true) 157 } else { 158 disableSensorPrivacy() 159 write(PRIVACY_TOGGLE_DIALOG_INTERACTION, 160 PRIVACY_TOGGLE_DIALOG_INTERACTION__ACTION__ENABLE, 161 sensorUsePackageName) 162 } 163 } 164 BUTTON_NEGATIVE -> { 165 unsuppressImmediately = false 166 write(PRIVACY_TOGGLE_DIALOG_INTERACTION, 167 PRIVACY_TOGGLE_DIALOG_INTERACTION__ACTION__CANCEL, 168 sensorUsePackageName) 169 } 170 } 171 172 finish() 173 } 174 175 override fun onStop() { 176 super.onStop() 177 178 if (unsuppressImmediately) { 179 setSuppressed(false) 180 } else { 181 bgHandler.postDelayed({ 182 setSuppressed(false) 183 }, SUPPRESS_REMINDERS_REMOVAL_DELAY_MILLIS) 184 } 185 } 186 187 override fun onDestroy() { 188 super.onDestroy() 189 mDialog?.dismiss() 190 sensorPrivacyListener?.also { sensorPrivacyController.removeCallback(it) } 191 onBackInvokedDispatcher.unregisterOnBackInvokedCallback(mBackCallback) 192 } 193 194 override fun onBackPressed() { 195 onBackInvoked() 196 } 197 198 fun onBackInvoked() { 199 // do not allow backing out 200 } 201 202 override fun onNewIntent(intent: Intent?) { 203 setIntent(intent) 204 recreate() 205 } 206 207 private fun disableSensorPrivacy() { 208 if (sensor == ALL_SENSORS) { 209 sensorPrivacyController.setSensorBlocked(DIALOG, MICROPHONE, false) 210 sensorPrivacyController.setSensorBlocked(DIALOG, CAMERA, false) 211 } else { 212 sensorPrivacyController.setSensorBlocked(DIALOG, sensor, false) 213 } 214 unsuppressImmediately = true 215 setResult(RESULT_OK) 216 } 217 218 private fun setSuppressed(suppressed: Boolean) { 219 if (sensor == ALL_SENSORS) { 220 sensorPrivacyController 221 .suppressSensorPrivacyReminders(MICROPHONE, suppressed) 222 sensorPrivacyController 223 .suppressSensorPrivacyReminders(CAMERA, suppressed) 224 } else { 225 sensorPrivacyController 226 .suppressSensorPrivacyReminders(sensor, suppressed) 227 } 228 } 229 230 override fun onDismiss(dialog: DialogInterface?) { 231 finish() 232 } 233 } 234