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