• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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