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.settings.utils; 18 19 import android.content.Context; 20 import android.hardware.SensorPrivacyManager; 21 import android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener; 22 import android.util.ArraySet; 23 import android.util.SparseArray; 24 25 import java.util.concurrent.Executor; 26 27 /** 28 * A class to help with calls to the sensor privacy manager. This class caches state when needed and 29 * multiplexes multiple listeners to a minimal set of binder calls. 30 */ 31 public class SensorPrivacyManagerHelper { 32 33 public static final int MICROPHONE = SensorPrivacyManager.Sensors.MICROPHONE; 34 public static final int CAMERA = SensorPrivacyManager.Sensors.CAMERA; 35 36 private static SensorPrivacyManagerHelper sInstance; 37 38 private final SensorPrivacyManager mSensorPrivacyManager; 39 40 private final SparseArray<Boolean> mCurrentUserCachedState = new SparseArray<>(); 41 private final SparseArray<SparseArray<Boolean>> mCachedState = new SparseArray<>(); 42 43 private final SparseArray<OnSensorPrivacyChangedListener> 44 mCurrentUserServiceListeners = new SparseArray<>(); 45 private final SparseArray<SparseArray<OnSensorPrivacyChangedListener>> 46 mServiceListeners = new SparseArray<>(); 47 48 private final ArraySet<CallbackInfo> mCallbacks = new ArraySet<>(); 49 50 private final Object mLock = new Object(); 51 52 /** 53 * Callback for when the state of the sensor privacy changes. 54 */ 55 public interface Callback { 56 57 /** 58 * Method invoked when the sensor privacy changes. 59 * @param sensor The sensor which changed 60 * @param blocked If the sensor is blocked 61 */ onSensorPrivacyChanged(int sensor, boolean blocked)62 void onSensorPrivacyChanged(int sensor, boolean blocked); 63 } 64 65 private static class CallbackInfo { 66 static final int CURRENT_USER = -1; 67 68 Callback mCallback; 69 Executor mExecutor; 70 int mSensor; 71 int mUserId; 72 CallbackInfo(Callback callback, Executor executor, int sensor, int userId)73 CallbackInfo(Callback callback, Executor executor, int sensor, int userId) { 74 mCallback = callback; 75 mExecutor = executor; 76 mSensor = sensor; 77 mUserId = userId; 78 } 79 } 80 81 /** 82 * Gets the singleton instance 83 * @param context The context which is needed if the instance hasn't been created 84 * @return the instance 85 */ getInstance(Context context)86 public static SensorPrivacyManagerHelper getInstance(Context context) { 87 if (sInstance == null) { 88 sInstance = new SensorPrivacyManagerHelper(context); 89 } 90 return sInstance; 91 } 92 93 /** 94 * Only to be used in tests 95 */ clearInstance()96 private static void clearInstance() { 97 sInstance = null; 98 } 99 SensorPrivacyManagerHelper(Context context)100 private SensorPrivacyManagerHelper(Context context) { 101 mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class); 102 } 103 104 /** 105 * Checks if the given toggle is supported on this device 106 * @param sensor The sensor to check 107 * @return whether the toggle for the sensor is supported on this device. 108 */ supportsSensorToggle(int sensor)109 public boolean supportsSensorToggle(int sensor) { 110 return mSensorPrivacyManager.supportsSensorToggle(sensor); 111 } 112 113 /** 114 * Checks if the sensor is blocked for the current user. If the user switches and the state of 115 * the new user is different, this value will change. 116 * @param sensor the sensor to check 117 * @return true if the sensor is blocked for the current user 118 */ isSensorBlocked(int sensor)119 public boolean isSensorBlocked(int sensor) { 120 synchronized (mLock) { 121 Boolean blocked = mCurrentUserCachedState.get(sensor); 122 if (blocked == null) { 123 registerCurrentUserListenerIfNeeded(sensor); 124 125 blocked = mSensorPrivacyManager.isSensorPrivacyEnabled(sensor); 126 mCurrentUserCachedState.put(sensor, blocked); 127 } 128 129 return blocked; 130 } 131 } 132 133 /** 134 * Checks if the sensor is or would be blocked if the given user is the foreground user 135 * @param sensor the sensor to check 136 * @param userId the user to check 137 * @return true if the sensor is or would be blocked if the given user is the foreground user 138 */ isSensorBlocked(int sensor, int userId)139 public boolean isSensorBlocked(int sensor, int userId) { 140 synchronized (mLock) { 141 SparseArray<Boolean> userCachedState = createUserCachedStateIfNeededLocked(userId); 142 Boolean blocked = userCachedState.get(sensor); 143 if (blocked == null) { 144 registerListenerIfNeeded(sensor, userId); 145 146 blocked = mSensorPrivacyManager.isSensorPrivacyEnabled(sensor); 147 userCachedState.put(sensor, blocked); 148 } 149 150 return blocked; 151 } 152 } 153 154 /** 155 * Sets the sensor privacy for the current user. 156 * @param source The source with which sensor privacy is toggled. 157 * @param sensor The sensor to set for 158 * @param blocked The state to set to 159 */ setSensorBlocked(int source, int sensor, boolean blocked)160 public void setSensorBlocked(int source, int sensor, boolean blocked) { 161 mSensorPrivacyManager.setSensorPrivacy(source, sensor, blocked); 162 } 163 164 /** 165 * Sets the sensor privacy for the given user. 166 * @param source The source with which sensor privacy is toggled. 167 * @param sensor The sensor to set for 168 * @param blocked The state to set to 169 * @param userId The user to set for 170 */ setSensorBlocked(int source, int sensor, boolean blocked, int userId)171 public void setSensorBlocked(int source, int sensor, boolean blocked, int userId) { 172 mSensorPrivacyManager.setSensorPrivacy(source, sensor, blocked, userId); 173 } 174 175 /** 176 * Sets the sensor privacy for the current profile group. 177 * @param source The source with which sensor privacy is toggled. 178 * @param sensor The sensor to set for 179 * @param blocked The state to set to 180 */ setSensorBlockedForProfileGroup(int source, int sensor, boolean blocked)181 public void setSensorBlockedForProfileGroup(int source, int sensor, boolean blocked) { 182 mSensorPrivacyManager.setSensorPrivacyForProfileGroup(source, sensor, blocked); 183 } 184 185 /** 186 * Sets the sensor privacy for the given user's profile group. 187 * @param source The source with which sensor privacy is toggled. 188 * @param sensor The sensor to set for 189 * @param blocked The state to set to 190 */ setSensorBlockedForProfileGroup(int source, int sensor, boolean blocked, int userId)191 public void setSensorBlockedForProfileGroup(int source, int sensor, boolean blocked, 192 int userId) { 193 mSensorPrivacyManager.setSensorPrivacyForProfileGroup(source, sensor, blocked, userId); 194 } 195 196 /** 197 * Adds a listener for the state of the current user. If the current user changes and the state 198 * of the new user is different, a callback will be received. 199 * @param sensor The sensor to watch 200 * @param callback The callback to invoke 201 * @param executor The executor to invoke on 202 */ addSensorBlockedListener(int sensor, Callback callback, Executor executor)203 public void addSensorBlockedListener(int sensor, Callback callback, Executor executor) { 204 synchronized (mLock) { 205 mCallbacks.add(new CallbackInfo(callback, executor, sensor, CallbackInfo.CURRENT_USER)); 206 } 207 } 208 209 /** 210 * Adds a listener for the state of the given user 211 * @param sensor The sensor to watch 212 * @param callback The callback to invoke 213 * @param executor The executor to invoke on 214 */ addSensorBlockedListener(int sensor, int userId, Callback callback, Executor executor)215 public void addSensorBlockedListener(int sensor, int userId, Callback callback, 216 Executor executor) { 217 synchronized (mLock) { 218 mCallbacks.add(new CallbackInfo(callback, executor, sensor, userId)); 219 } 220 } 221 222 /** 223 * Removes a callback 224 * @param callback The callback to remove 225 */ removeBlockedListener(Callback callback)226 public void removeBlockedListener(Callback callback) { 227 synchronized (mLock) { 228 mCallbacks.removeIf(callbackInfo -> callbackInfo.mCallback == callback); 229 } 230 } 231 registerCurrentUserListenerIfNeeded(int sensor)232 private void registerCurrentUserListenerIfNeeded(int sensor) { 233 synchronized (mLock) { 234 if (!mCurrentUserServiceListeners.contains(sensor)) { 235 OnSensorPrivacyChangedListener listener = (s, enabled) -> { 236 mCurrentUserCachedState.put(sensor, enabled); 237 dispatchStateChangedLocked(sensor, enabled, CallbackInfo.CURRENT_USER); 238 }; 239 mCurrentUserServiceListeners.put(sensor, listener); 240 mSensorPrivacyManager.addSensorPrivacyListener(sensor, listener); 241 } 242 } 243 } 244 registerListenerIfNeeded(int sensor, int userId)245 private void registerListenerIfNeeded(int sensor, int userId) { 246 synchronized (mLock) { 247 SparseArray<OnSensorPrivacyChangedListener> 248 userServiceListeners = createUserServiceListenersIfNeededLocked(userId); 249 250 if (!userServiceListeners.contains(sensor)) { 251 OnSensorPrivacyChangedListener listener = (s, enabled) -> { 252 SparseArray<Boolean> userCachedState = 253 createUserCachedStateIfNeededLocked(userId); 254 userCachedState.put(sensor, enabled); 255 dispatchStateChangedLocked(sensor, enabled, userId); 256 }; 257 mCurrentUserServiceListeners.put(sensor, listener); 258 mSensorPrivacyManager.addSensorPrivacyListener(sensor, listener); 259 } 260 } 261 } 262 dispatchStateChangedLocked(int sensor, boolean blocked, int userId)263 private void dispatchStateChangedLocked(int sensor, boolean blocked, int userId) { 264 for (CallbackInfo callbackInfo : mCallbacks) { 265 if (callbackInfo.mUserId == userId && callbackInfo.mSensor == sensor) { 266 Callback callback = callbackInfo.mCallback; 267 Executor executor = callbackInfo.mExecutor; 268 269 executor.execute(() -> callback.onSensorPrivacyChanged(sensor, blocked)); 270 } 271 } 272 } 273 createUserCachedStateIfNeededLocked(int userId)274 private SparseArray<Boolean> createUserCachedStateIfNeededLocked(int userId) { 275 SparseArray<Boolean> userCachedState = mCachedState.get(userId); 276 if (userCachedState == null) { 277 userCachedState = new SparseArray<>(); 278 mCachedState.put(userId, userCachedState); 279 } 280 return userCachedState; 281 } 282 createUserServiceListenersIfNeededLocked( int userId)283 private SparseArray<OnSensorPrivacyChangedListener> createUserServiceListenersIfNeededLocked( 284 int userId) { 285 SparseArray<OnSensorPrivacyChangedListener> userServiceListeners = 286 mServiceListeners.get(userId); 287 if (userServiceListeners == null) { 288 userServiceListeners = new SparseArray<>(); 289 mServiceListeners.put(userId, userServiceListeners); 290 } 291 return userServiceListeners; 292 } 293 } 294