/* * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.utils; import android.content.Context; import android.hardware.SensorPrivacyManager; import android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener; import android.util.ArraySet; import android.util.SparseArray; import java.util.concurrent.Executor; /** * A class to help with calls to the sensor privacy manager. This class caches state when needed and * multiplexes multiple listeners to a minimal set of binder calls. */ public class SensorPrivacyManagerHelper { public static final int MICROPHONE = SensorPrivacyManager.Sensors.MICROPHONE; public static final int CAMERA = SensorPrivacyManager.Sensors.CAMERA; private static SensorPrivacyManagerHelper sInstance; private final SensorPrivacyManager mSensorPrivacyManager; private final SparseArray mCurrentUserCachedState = new SparseArray<>(); private final SparseArray> mCachedState = new SparseArray<>(); private final SparseArray mCurrentUserServiceListeners = new SparseArray<>(); private final SparseArray> mServiceListeners = new SparseArray<>(); private final ArraySet mCallbacks = new ArraySet<>(); private final Object mLock = new Object(); /** * Callback for when the state of the sensor privacy changes. */ public interface Callback { /** * Method invoked when the sensor privacy changes. * @param sensor The sensor which changed * @param blocked If the sensor is blocked */ void onSensorPrivacyChanged(int sensor, boolean blocked); } private static class CallbackInfo { static final int CURRENT_USER = -1; Callback mCallback; Executor mExecutor; int mSensor; int mUserId; CallbackInfo(Callback callback, Executor executor, int sensor, int userId) { mCallback = callback; mExecutor = executor; mSensor = sensor; mUserId = userId; } } /** * Gets the singleton instance * @param context The context which is needed if the instance hasn't been created * @return the instance */ public static SensorPrivacyManagerHelper getInstance(Context context) { if (sInstance == null) { sInstance = new SensorPrivacyManagerHelper(context); } return sInstance; } /** * Only to be used in tests */ private static void clearInstance() { sInstance = null; } private SensorPrivacyManagerHelper(Context context) { mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class); } /** * Checks if the given toggle is supported on this device * @param sensor The sensor to check * @return whether the toggle for the sensor is supported on this device. */ public boolean supportsSensorToggle(int sensor) { return mSensorPrivacyManager.supportsSensorToggle(sensor); } /** * Checks if the sensor is blocked for the current user. If the user switches and the state of * the new user is different, this value will change. * @param sensor the sensor to check * @return true if the sensor is blocked for the current user */ public boolean isSensorBlocked(int sensor) { synchronized (mLock) { Boolean blocked = mCurrentUserCachedState.get(sensor); if (blocked == null) { registerCurrentUserListenerIfNeeded(sensor); blocked = mSensorPrivacyManager.isSensorPrivacyEnabled(sensor); mCurrentUserCachedState.put(sensor, blocked); } return blocked; } } /** * Checks if the sensor is or would be blocked if the given user is the foreground user * @param sensor the sensor to check * @param userId the user to check * @return true if the sensor is or would be blocked if the given user is the foreground user */ public boolean isSensorBlocked(int sensor, int userId) { synchronized (mLock) { SparseArray userCachedState = createUserCachedStateIfNeededLocked(userId); Boolean blocked = userCachedState.get(sensor); if (blocked == null) { registerListenerIfNeeded(sensor, userId); blocked = mSensorPrivacyManager.isSensorPrivacyEnabled(sensor); userCachedState.put(sensor, blocked); } return blocked; } } /** * Sets the sensor privacy for the current user. * @param source The source with which sensor privacy is toggled. * @param sensor The sensor to set for * @param blocked The state to set to */ public void setSensorBlocked(int source, int sensor, boolean blocked) { mSensorPrivacyManager.setSensorPrivacy(source, sensor, blocked); } /** * Sets the sensor privacy for the given user. * @param source The source with which sensor privacy is toggled. * @param sensor The sensor to set for * @param blocked The state to set to * @param userId The user to set for */ public void setSensorBlocked(int source, int sensor, boolean blocked, int userId) { mSensorPrivacyManager.setSensorPrivacy(source, sensor, blocked, userId); } /** * Sets the sensor privacy for the current profile group. * @param source The source with which sensor privacy is toggled. * @param sensor The sensor to set for * @param blocked The state to set to */ public void setSensorBlockedForProfileGroup(int source, int sensor, boolean blocked) { mSensorPrivacyManager.setSensorPrivacyForProfileGroup(source, sensor, blocked); } /** * Sets the sensor privacy for the given user's profile group. * @param source The source with which sensor privacy is toggled. * @param sensor The sensor to set for * @param blocked The state to set to */ public void setSensorBlockedForProfileGroup(int source, int sensor, boolean blocked, int userId) { mSensorPrivacyManager.setSensorPrivacyForProfileGroup(source, sensor, blocked, userId); } /** * Adds a listener for the state of the current user. If the current user changes and the state * of the new user is different, a callback will be received. * @param sensor The sensor to watch * @param callback The callback to invoke * @param executor The executor to invoke on */ public void addSensorBlockedListener(int sensor, Callback callback, Executor executor) { synchronized (mLock) { mCallbacks.add(new CallbackInfo(callback, executor, sensor, CallbackInfo.CURRENT_USER)); } } /** * Adds a listener for the state of the given user * @param sensor The sensor to watch * @param callback The callback to invoke * @param executor The executor to invoke on */ public void addSensorBlockedListener(int sensor, int userId, Callback callback, Executor executor) { synchronized (mLock) { mCallbacks.add(new CallbackInfo(callback, executor, sensor, userId)); } } /** * Removes a callback * @param callback The callback to remove */ public void removeBlockedListener(Callback callback) { synchronized (mLock) { mCallbacks.removeIf(callbackInfo -> callbackInfo.mCallback == callback); } } private void registerCurrentUserListenerIfNeeded(int sensor) { synchronized (mLock) { if (!mCurrentUserServiceListeners.contains(sensor)) { OnSensorPrivacyChangedListener listener = (s, enabled) -> { mCurrentUserCachedState.put(sensor, enabled); dispatchStateChangedLocked(sensor, enabled, CallbackInfo.CURRENT_USER); }; mCurrentUserServiceListeners.put(sensor, listener); mSensorPrivacyManager.addSensorPrivacyListener(sensor, listener); } } } private void registerListenerIfNeeded(int sensor, int userId) { synchronized (mLock) { SparseArray userServiceListeners = createUserServiceListenersIfNeededLocked(userId); if (!userServiceListeners.contains(sensor)) { OnSensorPrivacyChangedListener listener = (s, enabled) -> { SparseArray userCachedState = createUserCachedStateIfNeededLocked(userId); userCachedState.put(sensor, enabled); dispatchStateChangedLocked(sensor, enabled, userId); }; mCurrentUserServiceListeners.put(sensor, listener); mSensorPrivacyManager.addSensorPrivacyListener(sensor, listener); } } } private void dispatchStateChangedLocked(int sensor, boolean blocked, int userId) { for (CallbackInfo callbackInfo : mCallbacks) { if (callbackInfo.mUserId == userId && callbackInfo.mSensor == sensor) { Callback callback = callbackInfo.mCallback; Executor executor = callbackInfo.mExecutor; executor.execute(() -> callback.onSensorPrivacyChanged(sensor, blocked)); } } } private SparseArray createUserCachedStateIfNeededLocked(int userId) { SparseArray userCachedState = mCachedState.get(userId); if (userCachedState == null) { userCachedState = new SparseArray<>(); mCachedState.put(userId, userCachedState); } return userCachedState; } private SparseArray createUserServiceListenersIfNeededLocked( int userId) { SparseArray userServiceListeners = mServiceListeners.get(userId); if (userServiceListeners == null) { userServiceListeners = new SparseArray<>(); mServiceListeners.put(userId, userServiceListeners); } return userServiceListeners; } }