/* * Copyright (C) 2018 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 android.hardware; import android.Manifest; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; import android.annotation.UserIdInt; import android.content.Context; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; import android.service.SensorPrivacyIndividualEnabledSensorProto; import android.service.SensorPrivacyToggleSourceProto; import android.util.ArrayMap; import android.util.Log; import android.util.Pair; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.concurrent.Executor; /** * This class provides information about the microphone and camera toggles. */ @SystemService(Context.SENSOR_PRIVACY_SERVICE) public final class SensorPrivacyManager { private static final String LOG_TAG = SensorPrivacyManager.class.getSimpleName(); /** * Unique Id of this manager to identify to the service * @hide */ private IBinder token = new Binder(); /** * An extra containing a sensor * @hide */ public static final String EXTRA_SENSOR = SensorPrivacyManager.class.getName() + ".extra.sensor"; /** * An extra indicating if all sensors are affected * @hide */ public static final String EXTRA_ALL_SENSORS = SensorPrivacyManager.class.getName() + ".extra.all_sensors"; private final SparseArray mToggleSupportCache = new SparseArray<>(); /** * Individual sensors not listed in {@link Sensors} */ public static class Sensors { private Sensors() {} /** * Constant for the microphone */ public static final int MICROPHONE = SensorPrivacyIndividualEnabledSensorProto.MICROPHONE; /** * Constant for the camera */ public static final int CAMERA = SensorPrivacyIndividualEnabledSensorProto.CAMERA; /** * Individual sensors not listed in {@link Sensors} * * @hide */ @IntDef(value = { MICROPHONE, CAMERA }) @Retention(RetentionPolicy.SOURCE) public @interface Sensor {} } /** * Source through which Privacy Sensor was toggled. * @hide */ @TestApi public static class Sources { private Sources() {} /** * Constant for the Quick Setting Tile. */ public static final int QS_TILE = SensorPrivacyToggleSourceProto.QS_TILE; /** * Constant for the Settings. */ public static final int SETTINGS = SensorPrivacyToggleSourceProto.SETTINGS; /** * Constant for Dialog. */ public static final int DIALOG = SensorPrivacyToggleSourceProto.DIALOG; /** * Constant for SHELL. */ public static final int SHELL = SensorPrivacyToggleSourceProto.SHELL; /** * Constant for OTHER. */ public static final int OTHER = SensorPrivacyToggleSourceProto.OTHER; /** * Source for toggling sensors * * @hide */ @IntDef(value = { QS_TILE, SETTINGS, DIALOG, SHELL, OTHER }) @Retention(RetentionPolicy.SOURCE) public @interface Source {} } /** * A class implementing this interface can register with the {@link * android.hardware.SensorPrivacyManager} to receive notification when the sensor privacy * state changes. * * @hide */ @SystemApi public interface OnSensorPrivacyChangedListener { /** * Callback invoked when the sensor privacy state changes. * * @param sensor the sensor whose state is changing * @param enabled true if sensor privacy is enabled, false otherwise. */ void onSensorPrivacyChanged(int sensor, boolean enabled); } private static final Object sInstanceLock = new Object(); @GuardedBy("sInstanceLock") private static SensorPrivacyManager sInstance; @NonNull private final Context mContext; @NonNull private final ISensorPrivacyManager mService; @NonNull private final ArrayMap mListeners; @NonNull private final ArrayMap, ISensorPrivacyListener> mIndividualListeners; /** * Private constructor to ensure only a single instance is created. */ private SensorPrivacyManager(Context context, ISensorPrivacyManager service) { mContext = context; mService = service; mListeners = new ArrayMap<>(); mIndividualListeners = new ArrayMap<>(); } /** * Returns the single instance of the SensorPrivacyManager. * * @hide */ public static SensorPrivacyManager getInstance(Context context) { synchronized (sInstanceLock) { if (sInstance == null) { try { IBinder b = ServiceManager.getServiceOrThrow(Context.SENSOR_PRIVACY_SERVICE); ISensorPrivacyManager service = ISensorPrivacyManager.Stub.asInterface(b); sInstance = new SensorPrivacyManager(context, service); } catch (ServiceManager.ServiceNotFoundException e) { throw new IllegalStateException(e); } } return sInstance; } } /** * 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(@Sensors.Sensor int sensor) { try { Boolean val = mToggleSupportCache.get(sensor); if (val == null) { val = mService.supportsSensorToggle(sensor); mToggleSupportCache.put(sensor, val); } return val; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Registers a new listener to receive notification when the state of sensor privacy * changes. * * @param sensor the sensor to listen to changes to * @param listener the OnSensorPrivacyChangedListener to be notified when the state of sensor * privacy changes. * * @hide */ @SystemApi @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void addSensorPrivacyListener(@Sensors.Sensor int sensor, @NonNull OnSensorPrivacyChangedListener listener) { addSensorPrivacyListener(sensor, mContext.getMainExecutor(), listener); } /** * Registers a new listener to receive notification when the state of sensor privacy * changes. * * @param sensor the sensor to listen to changes to * @param userId the user's id * @param listener the OnSensorPrivacyChangedListener to be notified when the state of sensor * privacy changes. * * @hide */ @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void addSensorPrivacyListener(@Sensors.Sensor int sensor, @UserIdInt int userId, @NonNull OnSensorPrivacyChangedListener listener) { addSensorPrivacyListener(sensor, userId, mContext.getMainExecutor(), listener); } /** * Registers a new listener to receive notification when the state of sensor privacy * changes. * * @param sensor the sensor to listen to changes to * @param executor the executor to dispatch the callback on * @param listener the OnSensorPrivacyChangedListener to be notified when the state of sensor * privacy changes. * * @hide */ @SystemApi @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void addSensorPrivacyListener(@Sensors.Sensor int sensor, @NonNull Executor executor, @NonNull OnSensorPrivacyChangedListener listener) { Pair key = new Pair<>(listener, sensor); synchronized (mIndividualListeners) { ISensorPrivacyListener iListener = mIndividualListeners.get(key); if (iListener == null) { iListener = new ISensorPrivacyListener.Stub() { @Override public void onSensorPrivacyChanged(boolean enabled) { executor.execute(() -> listener.onSensorPrivacyChanged(sensor, enabled)); } }; mIndividualListeners.put(key, iListener); } try { mService.addUserGlobalIndividualSensorPrivacyListener(sensor, iListener); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } } /** * Registers a new listener to receive notification when the state of sensor privacy * changes. * * @param sensor the sensor to listen to changes to * @param executor the executor to dispatch the callback on * @param userId the user's id * @param listener the OnSensorPrivacyChangedListener to be notified when the state of sensor * privacy changes. * * @hide */ @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void addSensorPrivacyListener(@Sensors.Sensor int sensor, @UserIdInt int userId, @NonNull Executor executor, @NonNull OnSensorPrivacyChangedListener listener) { synchronized (mIndividualListeners) { ISensorPrivacyListener iListener = mIndividualListeners.get(listener); if (iListener == null) { iListener = new ISensorPrivacyListener.Stub() { @Override public void onSensorPrivacyChanged(boolean enabled) { executor.execute(() -> listener.onSensorPrivacyChanged(sensor, enabled)); } }; mIndividualListeners.put(new Pair<>(listener, sensor), iListener); } try { mService.addIndividualSensorPrivacyListener(userId, sensor, iListener); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } } /** * Unregisters the specified listener from receiving notifications when the state of any sensor * privacy changes. * * @param listener the OnSensorPrivacyChangedListener to be unregistered from notifications when * sensor privacy changes. * * @hide */ @SystemApi @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void removeSensorPrivacyListener(@Sensors.Sensor int sensor, @NonNull OnSensorPrivacyChangedListener listener) { synchronized (mListeners) { for (int i = 0; i < mIndividualListeners.size(); i++) { Pair pair = mIndividualListeners.keyAt(i); if (pair.second == sensor && pair.first.equals(listener)) { try { mService.removeIndividualSensorPrivacyListener(sensor, mIndividualListeners.valueAt(i)); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } mIndividualListeners.removeAt(i--); } } } } /** * Returns whether sensor privacy is currently enabled for a specific sensor. * * @return true if sensor privacy is currently enabled, false otherwise. * * @hide */ @SystemApi @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY) public boolean isSensorPrivacyEnabled(@Sensors.Sensor int sensor) { return isSensorPrivacyEnabled(sensor, UserHandle.USER_CURRENT); } /** * Returns whether sensor privacy is currently enabled for a specific sensor. * * @return true if sensor privacy is currently enabled, false otherwise. * * @hide */ @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY) public boolean isSensorPrivacyEnabled(@Sensors.Sensor int sensor, @UserIdInt int userId) { try { return mService.isIndividualSensorPrivacyEnabled(userId, sensor); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Sets sensor privacy to the specified state for an individual sensor. * * @param sensor the sensor which to change the state for * @param enable the state to which sensor privacy should be set. * * @hide */ @TestApi @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setSensorPrivacy(@Sources.Source int source, @Sensors.Sensor int sensor, boolean enable) { setSensorPrivacy(source, sensor, enable, UserHandle.USER_CURRENT); } /** * Sets sensor privacy to the specified state for an individual sensor. * * @param sensor the sensor which to change the state for * @param enable the state to which sensor privacy should be set. * @param userId the user's id * * @hide */ @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setSensorPrivacy(@Sources.Source int source, @Sensors.Sensor int sensor, boolean enable, @UserIdInt int userId) { try { mService.setIndividualSensorPrivacy(userId, source, sensor, enable); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Sets sensor privacy to the specified state for an individual sensor for the profile group of * context's user. * * @param source the source using which the sensor is toggled. * @param sensor the sensor which to change the state for * @param enable the state to which sensor privacy should be set. * * @hide */ @TestApi @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setSensorPrivacyForProfileGroup(@Sources.Source int source, @Sensors.Sensor int sensor, boolean enable) { setSensorPrivacyForProfileGroup(source , sensor, enable, UserHandle.USER_CURRENT); } /** * Sets sensor privacy to the specified state for an individual sensor for the profile group of * context's user. * * @param source the source using which the sensor is toggled. * @param sensor the sensor which to change the state for * @param enable the state to which sensor privacy should be set. * @param userId the user's id * * @hide */ @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setSensorPrivacyForProfileGroup(@Sources.Source int source, @Sensors.Sensor int sensor, boolean enable, @UserIdInt int userId) { try { mService.setIndividualSensorPrivacyForProfileGroup(userId, source, sensor, enable); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Don't show dialogs to turn off sensor privacy for this package. * * @param packageName Package name not to show dialogs for * @param suppress Whether to suppress or re-enable. * * @hide */ @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY) public void suppressSensorPrivacyReminders(int sensor, boolean suppress) { suppressSensorPrivacyReminders(sensor, suppress, UserHandle.USER_CURRENT); } /** * Don't show dialogs to turn off sensor privacy for this package. * * @param packageName Package name not to show dialogs for * @param suppress Whether to suppress or re-enable. * @param userId the user's id * * @hide */ @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY) public void suppressSensorPrivacyReminders(int sensor, boolean suppress, @UserIdInt int userId) { try { mService.suppressIndividualSensorPrivacyReminders(userId, sensor, token, suppress); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * If sensor privacy for the provided sensor is enabled then this call will show the user the * dialog which is shown when an application attempts to use that sensor. If privacy isn't * enabled then this does nothing. * * This call can only be made by the system uid. * * @throws SecurityException when called by someone other than system uid. * * @hide */ public void showSensorUseDialog(int sensor) { try { mService.showSensorUseDialog(sensor); } catch (RemoteException e) { Log.e(LOG_TAG, "Received exception while trying to show sensor use dialog", e); } } /** * A class implementing this interface can register with the {@link * android.hardware.SensorPrivacyManager} to receive notification when the all-sensor privacy * state changes. * * @hide */ public interface OnAllSensorPrivacyChangedListener { /** * Callback invoked when the sensor privacy state changes. * * @param enabled true if sensor privacy is enabled, false otherwise. */ void onAllSensorPrivacyChanged(boolean enabled); } /** * Sets all-sensor privacy to the specified state. * * @param enable the state to which sensor privacy should be set. * * @hide */ @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setAllSensorPrivacy(boolean enable) { try { mService.setSensorPrivacy(enable); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Registers a new listener to receive notification when the state of all-sensor privacy * changes. * * @param listener the OnSensorPrivacyChangedListener to be notified when the state of * all-sensor privacy changes. * * @hide */ @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void addAllSensorPrivacyListener( @NonNull final OnAllSensorPrivacyChangedListener listener) { synchronized (mListeners) { ISensorPrivacyListener iListener = mListeners.get(listener); if (iListener == null) { iListener = new ISensorPrivacyListener.Stub() { @Override public void onSensorPrivacyChanged(boolean enabled) { listener.onAllSensorPrivacyChanged(enabled); } }; mListeners.put(listener, iListener); } try { mService.addSensorPrivacyListener(iListener); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } } /** * Unregisters the specified listener from receiving notifications when the state of all-sensor * privacy changes. * * @param listener the OnAllSensorPrivacyChangedListener to be unregistered from notifications * when all-sensor privacy changes. * * @hide */ @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void removeAllSensorPrivacyListener( @NonNull OnAllSensorPrivacyChangedListener listener) { synchronized (mListeners) { ISensorPrivacyListener iListener = mListeners.get(listener); if (iListener != null) { mListeners.remove(iListener); try { mService.removeSensorPrivacyListener(iListener); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } } } /** * Returns whether all-sensor privacy is currently enabled. * * @return true if all-sensor privacy is currently enabled, false otherwise. * * @hide */ @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY) public boolean isAllSensorPrivacyEnabled() { try { return mService.isSensorPrivacyEnabled(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } }