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.content.DialogInterface 20 import android.content.Intent 21 import android.content.Intent.EXTRA_PACKAGE_NAME 22 import android.content.pm.PackageManager 23 import android.content.res.Resources 24 import android.hardware.SensorPrivacyManager 25 import android.hardware.SensorPrivacyManager.EXTRA_ALL_SENSORS 26 import android.hardware.SensorPrivacyManager.EXTRA_SENSOR 27 import android.hardware.SensorPrivacyManager.Sources.DIALOG 28 import android.os.Bundle 29 import android.os.Handler 30 import android.text.Html 31 import android.view.View.GONE 32 import android.view.View.VISIBLE 33 import android.widget.ImageView 34 import com.android.internal.app.AlertActivity 35 import com.android.internal.widget.DialogTitle 36 import com.android.systemui.R 37 import com.android.systemui.dagger.qualifiers.Background 38 import com.android.systemui.statusbar.phone.KeyguardDismissUtil 39 import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController 40 import com.android.systemui.statusbar.policy.KeyguardStateController 41 import javax.inject.Inject 42 import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION 43 import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION__ACTION__ENABLE 44 import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION__ACTION__CANCEL 45 import com.android.internal.util.FrameworkStatsLog.write 46 47 /** 48 * Dialog to be shown on top of apps that are attempting to use a sensor (e.g. microphone) which is 49 * currently in "sensor privacy mode", aka. muted. 50 * 51 * <p>The dialog is started for the user the app is running for which might be a secondary users. 52 */ 53 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 ) : AlertActivity(), DialogInterface.OnClickListener { 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 private const val CAMERA = SensorPrivacyManager.Sensors.CAMERA 67 private const val MICROPHONE = SensorPrivacyManager.Sensors.MICROPHONE 68 private 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 lateinit var sensorPrivacyListener: IndividualSensorPrivacyController.Callback 76 77 override fun onCreate(savedInstanceState: Bundle?) { 78 super.onCreate(savedInstanceState) 79 80 setShowWhenLocked(true) 81 82 setFinishOnTouchOutside(false) 83 84 setResult(RESULT_CANCELED) 85 86 sensorUsePackageName = intent.getStringExtra(EXTRA_PACKAGE_NAME) ?: return 87 88 if (intent.getBooleanExtra(EXTRA_ALL_SENSORS, false)) { 89 sensor = ALL_SENSORS 90 sensorPrivacyListener = 91 IndividualSensorPrivacyController.Callback { _, _ -> 92 if (!sensorPrivacyController.isSensorBlocked(MICROPHONE) && 93 !sensorPrivacyController.isSensorBlocked(CAMERA)) { 94 dismiss() 95 } 96 } 97 98 sensorPrivacyController.addCallback(sensorPrivacyListener) 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 sensorPrivacyListener = 112 IndividualSensorPrivacyController.Callback { 113 whichSensor: Int, isBlocked: Boolean -> 114 if (whichSensor == sensor && !isBlocked) { 115 dismiss() 116 } 117 } 118 sensorPrivacyController.addCallback(sensorPrivacyListener) 119 120 sensorPrivacyController.addCallback { _, isBlocked -> 121 if (!isBlocked) { 122 dismiss() 123 } 124 } 125 } 126 127 mAlertParams.apply { 128 try { 129 mCustomTitleView = mInflater.inflate(R.layout.sensor_use_started_title, null) 130 mCustomTitleView.findViewById<DialogTitle>(R.id.sensor_use_started_title_message)!! 131 .setText(when (sensor) { 132 MICROPHONE -> 133 R.string.sensor_privacy_start_use_mic_dialog_title 134 CAMERA -> 135 R.string.sensor_privacy_start_use_camera_dialog_title 136 ALL_SENSORS -> 137 R.string.sensor_privacy_start_use_mic_camera_dialog_title 138 else -> Resources.ID_NULL 139 }) 140 141 mCustomTitleView.findViewById<ImageView>(R.id.sensor_use_microphone_icon)!! 142 .visibility = if (sensor == MICROPHONE || sensor == ALL_SENSORS) { 143 VISIBLE 144 } else { 145 GONE 146 } 147 mCustomTitleView.findViewById<ImageView>(R.id.sensor_use_camera_icon)!! 148 .visibility = if (sensor == CAMERA || sensor == ALL_SENSORS) { 149 VISIBLE 150 } else { 151 GONE 152 } 153 154 mMessage = Html.fromHtml(getString(when (sensor) { 155 MICROPHONE -> 156 R.string.sensor_privacy_start_use_mic_dialog_content 157 CAMERA -> 158 R.string.sensor_privacy_start_use_camera_dialog_content 159 ALL_SENSORS -> 160 R.string.sensor_privacy_start_use_mic_camera_dialog_content 161 else -> Resources.ID_NULL 162 }, packageManager.getApplicationInfo(sensorUsePackageName, 0) 163 .loadLabel(packageManager)), 0) 164 } catch (e: PackageManager.NameNotFoundException) { 165 finish() 166 return 167 } 168 169 mPositiveButtonText = getString( 170 com.android.internal.R.string.sensor_privacy_start_use_dialog_turn_on_button) 171 mNegativeButtonText = getString(android.R.string.cancel) 172 mPositiveButtonListener = this@SensorUseStartedActivity 173 mNegativeButtonListener = this@SensorUseStartedActivity 174 } 175 176 setupAlert() 177 } 178 179 override fun onStart() { 180 super.onStart() 181 182 setSuppressed(true) 183 unsuppressImmediately = false 184 } 185 186 override fun onClick(dialog: DialogInterface?, which: Int) { 187 when (which) { 188 BUTTON_POSITIVE -> { 189 if (keyguardStateController.isMethodSecure && keyguardStateController.isShowing) { 190 keyguardDismissUtil.executeWhenUnlocked({ 191 bgHandler.postDelayed({ 192 disableSensorPrivacy() 193 write(PRIVACY_TOGGLE_DIALOG_INTERACTION, 194 PRIVACY_TOGGLE_DIALOG_INTERACTION__ACTION__ENABLE, 195 sensorUsePackageName) 196 }, UNLOCK_DELAY_MILLIS) 197 198 false 199 }, false, true) 200 } else { 201 disableSensorPrivacy() 202 write(PRIVACY_TOGGLE_DIALOG_INTERACTION, 203 PRIVACY_TOGGLE_DIALOG_INTERACTION__ACTION__ENABLE, 204 sensorUsePackageName) 205 } 206 } 207 BUTTON_NEGATIVE -> { 208 unsuppressImmediately = false 209 write(PRIVACY_TOGGLE_DIALOG_INTERACTION, 210 PRIVACY_TOGGLE_DIALOG_INTERACTION__ACTION__CANCEL, 211 sensorUsePackageName) 212 } 213 } 214 215 dismiss() 216 } 217 218 override fun onStop() { 219 super.onStop() 220 221 if (unsuppressImmediately) { 222 setSuppressed(false) 223 } else { 224 bgHandler.postDelayed({ 225 setSuppressed(false) 226 }, SUPPRESS_REMINDERS_REMOVAL_DELAY_MILLIS) 227 } 228 } 229 230 override fun onDestroy() { 231 super.onDestroy() 232 sensorPrivacyController.removeCallback(sensorPrivacyListener) 233 } 234 235 override fun onBackPressed() { 236 // do not allow backing out 237 } 238 239 override fun onNewIntent(intent: Intent?) { 240 setIntent(intent) 241 recreate() 242 } 243 244 private fun disableSensorPrivacy() { 245 if (sensor == ALL_SENSORS) { 246 sensorPrivacyController.setSensorBlocked(DIALOG, MICROPHONE, false) 247 sensorPrivacyController.setSensorBlocked(DIALOG, CAMERA, false) 248 } else { 249 sensorPrivacyController.setSensorBlocked(DIALOG, sensor, false) 250 } 251 unsuppressImmediately = true 252 setResult(RESULT_OK) 253 } 254 255 private fun setSuppressed(suppressed: Boolean) { 256 if (sensor == ALL_SENSORS) { 257 sensorPrivacyController 258 .suppressSensorPrivacyReminders(MICROPHONE, suppressed) 259 sensorPrivacyController 260 .suppressSensorPrivacyReminders(CAMERA, suppressed) 261 } else { 262 sensorPrivacyController 263 .suppressSensorPrivacyReminders(sensor, suppressed) 264 } 265 } 266 }