• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2022 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 package com.android.settings.utils
17 
18 import android.content.Context
19 import android.hardware.SensorPrivacyManager
20 import android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener.SensorPrivacyChangedParams
21 import android.hardware.SensorPrivacyManager.Sources.SETTINGS
22 import android.util.Log
23 import java.util.concurrent.Executor
24 
25 /**
26  * A class to help with calls to the sensor privacy manager. This class caches state when needed and
27  * multiplexes multiple listeners to a minimal set of binder calls.
28  *
29  * If you are not a test use [SensorPrivacyManagerHelper.getInstance]
30  */
31 // This class uses `open` a lot for mockito
32 open class SensorPrivacyManagerHelper(context: Context) :
33         SensorPrivacyManager.OnSensorPrivacyChangedListener {
34     private val sensorPrivacyManager: SensorPrivacyManager
35     private val cache: MutableMap<Pair<Int, Int>, Boolean> = mutableMapOf()
36     private val callbacks: MutableMap<Pair<Int, Int>, MutableSet<Pair<Callback, Executor>>> =
37             mutableMapOf()
38     private val lock = Any()
39 
40     /**
41      * Callback for when the state of the sensor privacy changes.
42      */
43     interface Callback {
44         /**
45          * Method invoked when the sensor privacy changes.
46          * @param sensor The sensor which changed
47          * @param blocked If the sensor is blocked
48          */
49         fun onSensorPrivacyChanged(toggleType: Int, sensor: Int, blocked: Boolean)
50     }
51 
52     init {
53         sensorPrivacyManager = context.getSystemService(SensorPrivacyManager::class.java)!!
54 
55         sensorPrivacyManager.addSensorPrivacyListener(context.mainExecutor, this)
56     }
57 
58     /**
59      * Checks if the given toggle is supported on this device
60      * @param sensor The sensor to check
61      * @return whether the toggle for the sensor is supported on this device.
62      */
63     open fun supportsSensorToggle(sensor: Int): Boolean {
64         return sensorPrivacyManager.supportsSensorToggle(sensor)
65     }
66 
67     @JvmOverloads
68     open fun isSensorBlocked(toggleType: Int = TOGGLE_TYPE_ANY, sensor: Int): Boolean {
69         synchronized(lock) {
70             if (toggleType == TOGGLE_TYPE_ANY) {
71                 return isSensorBlocked(TOGGLE_TYPE_SOFTWARE, sensor) ||
72                         isSensorBlocked(TOGGLE_TYPE_HARDWARE, sensor)
73             }
74             return cache.getOrPut(toggleType to sensor) {
75                 sensorPrivacyManager.isSensorPrivacyEnabled(toggleType, sensor)
76             }
77         }
78     }
79 
80     open fun setSensorBlocked(sensor: Int, blocked: Boolean) {
81         sensorPrivacyManager.setSensorPrivacy(SETTINGS, sensor, blocked)
82     }
83 
84     open fun addSensorBlockedListener(executor: Executor?, callback: Callback?) {
85         // Not using defaults for mockito
86         addSensorBlockedListener(SENSOR_ANY, executor, callback)
87     }
88 
89     open fun addSensorBlockedListener(sensor: Int, executor: Executor?, callback: Callback?) {
90         // Not using defaults for mockito
91         addSensorBlockedListener(TOGGLE_TYPE_ANY, sensor, executor, callback)
92     }
93 
94     open fun addSensorBlockedListener(toggleType: Int, sensor: Int,
95                                       executor: Executor?, callback: Callback?) {
96         // Note: executor and callback should be nonnull, but we want to use mockito
97         if (toggleType == TOGGLE_TYPE_ANY) {
98             addSensorBlockedListener(TOGGLE_TYPE_SOFTWARE, sensor, executor, callback)
99             addSensorBlockedListener(TOGGLE_TYPE_HARDWARE, sensor, executor, callback)
100             return
101         }
102 
103         if (sensor == SENSOR_ANY) {
104             addSensorBlockedListener(toggleType, SENSOR_MICROPHONE, executor, callback)
105             addSensorBlockedListener(toggleType, SENSOR_CAMERA, executor, callback)
106             return
107         }
108 
109         synchronized(lock) {
110             callbacks.getOrPut(toggleType to sensor) { mutableSetOf() }
111                     .add(callback!! to executor!!)
112         }
113     }
114 
115     open fun removeSensorBlockedListener(callback: Callback) {
116         val keysToRemove = mutableListOf<Pair<Int, Int>>()
117         synchronized(lock) {
118             callbacks.forEach { entry ->
119                 entry.value.removeIf {
120                     it.first == callback
121                 }
122 
123                 if (entry.value.isEmpty()) {
124                     keysToRemove.add(entry.key)
125                 }
126             }
127 
128             keysToRemove.forEach {
129                 callbacks.remove(it)
130             }
131         }
132     }
133 
134     companion object {
135         const val TOGGLE_TYPE_SOFTWARE = SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE
136         const val TOGGLE_TYPE_HARDWARE = SensorPrivacyManager.TOGGLE_TYPE_HARDWARE
137         const val SENSOR_MICROPHONE = SensorPrivacyManager.Sensors.MICROPHONE
138         const val SENSOR_CAMERA = SensorPrivacyManager.Sensors.CAMERA
139 
140         private const val TOGGLE_TYPE_ANY = -1
141         private const val SENSOR_ANY = -1
142         private var sInstance: SensorPrivacyManagerHelper? = null
143 
144         /**
145          * Gets the singleton instance
146          * @param context The context which is needed if the instance hasn't been created
147          * @return the instance
148          */
149         @JvmStatic
150         fun getInstance(context: Context): SensorPrivacyManagerHelper? {
151             if (sInstance == null) {
152                 sInstance = SensorPrivacyManagerHelper(context)
153             }
154             return sInstance
155         }
156     }
157 
158     override fun onSensorPrivacyChanged(sensor: Int, enabled: Boolean) {
159         // ignored
160     }
161 
162     override fun onSensorPrivacyChanged(params: SensorPrivacyChangedParams) {
163         var changed: Boolean
164         synchronized(lock) {
165             changed = cache.put(params.toggleType to params.sensor, params.isEnabled) !=
166                     params.isEnabled
167 
168             if (changed) {
169                 callbacks[params.toggleType to params.sensor]?.forEach {
170                     it.second.execute {
171                         it.first.onSensorPrivacyChanged(params.toggleType, params.sensor,
172                                 params.isEnabled)
173                     }
174                 }
175             }
176         }
177     }
178 }
179