1 /* <lambda>null2 * 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.content.pm.PackageManager 27 import android.hardware.SensorPrivacyManager 28 import android.hardware.SensorPrivacyManager.EXTRA_ALL_SENSORS 29 import android.hardware.SensorPrivacyManager.EXTRA_SENSOR 30 import android.hardware.SensorPrivacyManager.Sources.DIALOG 31 import android.os.Bundle 32 import android.os.Handler 33 import android.window.OnBackInvokedDispatcher 34 import androidx.annotation.OpenForTesting 35 import com.android.internal.camera.flags.Flags 36 import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION 37 import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION__ACTION__CANCEL 38 import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION__ACTION__ENABLE 39 import com.android.internal.util.FrameworkStatsLog.write 40 import com.android.systemui.dagger.qualifiers.Background 41 import com.android.systemui.statusbar.phone.KeyguardDismissUtil 42 import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController 43 import com.android.systemui.statusbar.policy.KeyguardStateController 44 import javax.inject.Inject 45 46 /** 47 * Dialog to be shown on top of apps that are attempting to use a sensor (e.g. microphone) which is 48 * currently in "sensor privacy mode", aka. muted. 49 * 50 * <p>The dialog is started for the user the app is running for which might be a secondary users. 51 */ 52 @OpenForTesting 53 open class SensorUseStartedActivity @Inject constructor( 54 private val sensorPrivacyController: IndividualSensorPrivacyController, 55 private val keyguardStateController: KeyguardStateController, 56 private val keyguardDismissUtil: KeyguardDismissUtil, 57 @Background private val bgHandler: Handler 58 ) : Activity(), DialogInterface.OnClickListener, DialogInterface.OnDismissListener { 59 60 companion object { 61 private val LOG_TAG = SensorUseStartedActivity::class.java.simpleName 62 63 private const val SUPPRESS_REMINDERS_REMOVAL_DELAY_MILLIS = 2000L 64 private const val UNLOCK_DELAY_MILLIS = 200L 65 66 internal const val CAMERA = SensorPrivacyManager.Sensors.CAMERA 67 internal const val MICROPHONE = SensorPrivacyManager.Sensors.MICROPHONE 68 internal const val ALL_SENSORS = Integer.MAX_VALUE 69 } 70 71 private var sensor = -1 72 private lateinit var sensorUsePackageName: String 73 private var unsuppressImmediately = false 74 75 private var sensorPrivacyListener: IndividualSensorPrivacyController.Callback? = null 76 77 private var mDialog: AlertDialog? = null 78 private val mBackCallback = this::onBackInvoked 79 80 override fun onCreate(savedInstanceState: Bundle?) { 81 super.onCreate(savedInstanceState) 82 83 setShowWhenLocked(true) 84 85 setFinishOnTouchOutside(false) 86 87 setResult(RESULT_CANCELED) 88 89 sensorUsePackageName = intent.getStringExtra(EXTRA_PACKAGE_NAME) ?: return 90 91 if (intent.getBooleanExtra(EXTRA_ALL_SENSORS, false)) { 92 sensor = ALL_SENSORS 93 val callback = IndividualSensorPrivacyController.Callback { _, _ -> 94 if (!sensorPrivacyController.isSensorBlocked(MICROPHONE) && 95 !isCameraBlocked(sensorUsePackageName)) { 96 finish() 97 } 98 } 99 sensorPrivacyListener = callback 100 sensorPrivacyController.addCallback(callback) 101 if (!sensorPrivacyController.isSensorBlocked(MICROPHONE) && 102 !isCameraBlocked(sensorUsePackageName)) { 103 finish() 104 return 105 } 106 } else { 107 sensor = intent.getIntExtra(EXTRA_SENSOR, -1).also { 108 if (it == -1) { 109 finish() 110 return 111 } 112 } 113 val callback = IndividualSensorPrivacyController.Callback { 114 whichSensor: Int, isBlocked: Boolean -> 115 if (whichSensor != sensor) { 116 // Ignore a callback; we're not interested in. 117 } else if ((whichSensor == CAMERA) && !isCameraBlocked(sensorUsePackageName)) { 118 finish() 119 } else if ((whichSensor == MICROPHONE) && !isBlocked) { 120 finish() 121 } 122 } 123 sensorPrivacyListener = callback 124 sensorPrivacyController.addCallback(callback) 125 126 if ((sensor == CAMERA) && !isCameraBlocked(sensorUsePackageName)) { 127 finish() 128 return 129 } else if ((sensor == MICROPHONE) && 130 !sensorPrivacyController.isSensorBlocked(MICROPHONE)) { 131 finish() 132 return 133 } 134 } 135 136 mDialog = SensorUseDialog(this, sensor, this, this) 137 mDialog!!.show() 138 139 onBackInvokedDispatcher.registerOnBackInvokedCallback( 140 OnBackInvokedDispatcher.PRIORITY_DEFAULT, 141 mBackCallback) 142 } 143 144 override fun onStart() { 145 super.onStart() 146 147 setSuppressed(true) 148 unsuppressImmediately = false 149 } 150 151 override fun onClick(dialog: DialogInterface?, which: Int) { 152 when (which) { 153 BUTTON_POSITIVE -> { 154 if (sensorPrivacyController.requiresAuthentication() && 155 keyguardStateController.isMethodSecure && 156 keyguardStateController.isShowing) { 157 keyguardDismissUtil.executeWhenUnlocked({ 158 bgHandler.postDelayed({ 159 disableSensorPrivacy() 160 write(PRIVACY_TOGGLE_DIALOG_INTERACTION, 161 PRIVACY_TOGGLE_DIALOG_INTERACTION__ACTION__ENABLE, 162 sensorUsePackageName) 163 }, UNLOCK_DELAY_MILLIS) 164 165 false 166 }, false, true) 167 } else { 168 disableSensorPrivacy() 169 write(PRIVACY_TOGGLE_DIALOG_INTERACTION, 170 PRIVACY_TOGGLE_DIALOG_INTERACTION__ACTION__ENABLE, 171 sensorUsePackageName) 172 } 173 } 174 BUTTON_NEGATIVE -> { 175 unsuppressImmediately = false 176 write(PRIVACY_TOGGLE_DIALOG_INTERACTION, 177 PRIVACY_TOGGLE_DIALOG_INTERACTION__ACTION__CANCEL, 178 sensorUsePackageName) 179 } 180 } 181 182 finish() 183 } 184 185 override fun onStop() { 186 super.onStop() 187 188 if (unsuppressImmediately) { 189 setSuppressed(false) 190 } else { 191 bgHandler.postDelayed({ 192 setSuppressed(false) 193 }, SUPPRESS_REMINDERS_REMOVAL_DELAY_MILLIS) 194 } 195 } 196 197 override fun onDestroy() { 198 super.onDestroy() 199 mDialog?.dismiss() 200 sensorPrivacyListener?.also { sensorPrivacyController.removeCallback(it) } 201 onBackInvokedDispatcher.unregisterOnBackInvokedCallback(mBackCallback) 202 } 203 204 override fun onBackPressed() { 205 onBackInvoked() 206 } 207 208 fun onBackInvoked() { 209 // do not allow backing out 210 } 211 212 override fun onNewIntent(intent: Intent?) { 213 setIntent(intent) 214 recreate() 215 } 216 217 private fun isAutomotive(): Boolean { 218 return getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE) 219 } 220 221 private fun isCameraBlocked(packageName: String): Boolean { 222 if (Flags.cameraPrivacyAllowlist()) { 223 if (isAutomotive()) { 224 return sensorPrivacyController.isCameraPrivacyEnabled(packageName) 225 } else { 226 return sensorPrivacyController.isSensorBlocked(CAMERA) 227 } 228 } else { 229 return sensorPrivacyController.isSensorBlocked(CAMERA) 230 } 231 } 232 233 private fun disableSensorPrivacy() { 234 if (sensor == ALL_SENSORS) { 235 sensorPrivacyController.setSensorBlocked(DIALOG, MICROPHONE, false) 236 sensorPrivacyController.setSensorBlocked(DIALOG, CAMERA, false) 237 } else { 238 sensorPrivacyController.setSensorBlocked(DIALOG, sensor, false) 239 } 240 unsuppressImmediately = true 241 setResult(RESULT_OK) 242 } 243 244 private fun setSuppressed(suppressed: Boolean) { 245 if (sensor == ALL_SENSORS) { 246 sensorPrivacyController 247 .suppressSensorPrivacyReminders(MICROPHONE, suppressed) 248 sensorPrivacyController 249 .suppressSensorPrivacyReminders(CAMERA, suppressed) 250 } else { 251 sensorPrivacyController 252 .suppressSensorPrivacyReminders(sensor, suppressed) 253 } 254 } 255 256 override fun onDismiss(dialog: DialogInterface?) { 257 if (!isChangingConfigurations) { 258 finish() 259 } 260 } 261 } 262