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