• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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