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