Merge "Implement camera privacy allowlist." into main
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index c09653d..50c9c33 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -100,6 +100,7 @@
field public static final String CAMERA_DISABLE_TRANSMIT_LED = "android.permission.CAMERA_DISABLE_TRANSMIT_LED";
field @FlaggedApi("com.android.internal.camera.flags.camera_hsum_permission") public static final String CAMERA_HEADLESS_SYSTEM_USER = "android.permission.CAMERA_HEADLESS_SYSTEM_USER";
field public static final String CAMERA_OPEN_CLOSE_LISTENER = "android.permission.CAMERA_OPEN_CLOSE_LISTENER";
+ field @FlaggedApi("com.android.internal.camera.flags.privacy_allowlist") public static final String CAMERA_PRIVACY_ALLOWLIST = "android.permission.CAMERA_PRIVACY_ALLOWLIST";
field public static final String CAPTURE_AUDIO_HOTWORD = "android.permission.CAPTURE_AUDIO_HOTWORD";
field public static final String CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD = "android.permission.CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD";
field public static final String CAPTURE_MEDIA_OUTPUT = "android.permission.CAPTURE_MEDIA_OUTPUT";
@@ -4656,11 +4657,15 @@
method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void addSensorPrivacyListener(@NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener);
method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void addSensorPrivacyListener(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener);
method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public boolean areAnySensorPrivacyTogglesEnabled(int);
+ method @FlaggedApi("com.android.internal.camera.flags.privacy_allowlist") @NonNull @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public java.util.Map<java.lang.String,java.lang.Boolean> getCameraPrivacyAllowlist();
+ method @FlaggedApi("com.android.internal.camera.flags.privacy_allowlist") @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public int getSensorPrivacyState(int, int);
+ method @FlaggedApi("com.android.internal.camera.flags.privacy_allowlist") @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public boolean isCameraPrivacyEnabled(@NonNull String);
method @Deprecated @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public boolean isSensorPrivacyEnabled(int);
method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public boolean isSensorPrivacyEnabled(int, int);
method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void removeSensorPrivacyListener(int, @NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener);
method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void removeSensorPrivacyListener(@NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener);
method @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setSensorPrivacy(int, boolean);
+ method @FlaggedApi("com.android.internal.camera.flags.privacy_allowlist") @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setSensorPrivacyState(int, int);
}
public static interface SensorPrivacyManager.OnSensorPrivacyChangedListener {
@@ -4670,6 +4675,7 @@
public static class SensorPrivacyManager.OnSensorPrivacyChangedListener.SensorPrivacyChangedParams {
method public int getSensor();
+ method @FlaggedApi("com.android.internal.camera.flags.privacy_allowlist") public int getState();
method public int getToggleType();
method public boolean isEnabled();
}
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 288374d..1e30a32 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1483,6 +1483,7 @@
public final class SensorPrivacyManager {
method @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setSensorPrivacy(int, int, boolean);
+ method @FlaggedApi("com.android.internal.camera.flags.privacy_allowlist") @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setSensorPrivacyState(int, int, int);
}
public static class SensorPrivacyManager.Sources {
diff --git a/core/java/android/hardware/CameraPrivacyAllowlistEntry.aidl b/core/java/android/hardware/CameraPrivacyAllowlistEntry.aidl
new file mode 100644
index 0000000..838e41e
--- /dev/null
+++ b/core/java/android/hardware/CameraPrivacyAllowlistEntry.aidl
@@ -0,0 +1,24 @@
+/**
+ * Copyright (c) 2024, 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;
+
+/** @hide */
+parcelable CameraPrivacyAllowlistEntry {
+ String packageName;
+ boolean isMandatory;
+}
+
diff --git a/core/java/android/hardware/ISensorPrivacyListener.aidl b/core/java/android/hardware/ISensorPrivacyListener.aidl
index 2ac21d2..19ae302 100644
--- a/core/java/android/hardware/ISensorPrivacyListener.aidl
+++ b/core/java/android/hardware/ISensorPrivacyListener.aidl
@@ -25,5 +25,6 @@
// frameworks/native/libs/sensorprivacy/aidl/android/hardware/ISensorPrivacyListener.aidl
// =============== Beginning of transactions used on native side as well ======================
void onSensorPrivacyChanged(int toggleType, int sensor, boolean enabled);
+ void onSensorPrivacyStateChanged(int toggleType, int sensor, int state);
// =============== End of transactions used on native side as well ============================
}
diff --git a/core/java/android/hardware/ISensorPrivacyManager.aidl b/core/java/android/hardware/ISensorPrivacyManager.aidl
index 9cf329c..851ce2a 100644
--- a/core/java/android/hardware/ISensorPrivacyManager.aidl
+++ b/core/java/android/hardware/ISensorPrivacyManager.aidl
@@ -16,6 +16,7 @@
package android.hardware;
+import android.hardware.CameraPrivacyAllowlistEntry;
import android.hardware.ISensorPrivacyListener;
/** @hide */
@@ -45,6 +46,22 @@
void setToggleSensorPrivacy(int userId, int source, int sensor, boolean enable);
void setToggleSensorPrivacyForProfileGroup(int userId, int source, int sensor, boolean enable);
+
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY)")
+ List<CameraPrivacyAllowlistEntry> getCameraPrivacyAllowlist();
+
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY)")
+ int getToggleSensorPrivacyState(int toggleType, int sensor);
+
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY)")
+ void setToggleSensorPrivacyState(int userId, int source, int sensor, int state);
+
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY)")
+ void setToggleSensorPrivacyStateForProfileGroup(int userId, int source, int sensor, int state);
+
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY)")
+ boolean isCameraPrivacyEnabled(String packageName);
+
// =============== End of transactions used on native side as well ============================
void suppressToggleSensorPrivacyReminders(int userId, int sensor, IBinder token,
@@ -53,4 +70,4 @@
boolean requiresAuthentication();
void showSensorUseDialog(int sensor);
-}
\ No newline at end of file
+}
diff --git a/core/java/android/hardware/SensorPrivacyManager.java b/core/java/android/hardware/SensorPrivacyManager.java
index 18c95bfb..4c0a4b9 100644
--- a/core/java/android/hardware/SensorPrivacyManager.java
+++ b/core/java/android/hardware/SensorPrivacyManager.java
@@ -17,6 +17,7 @@
package android.hardware;
import android.Manifest;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
@@ -38,9 +39,11 @@
import android.util.Pair;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.camera.flags.Flags;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -215,13 +218,41 @@
public static final int DISABLED = SensorPrivacyIndividualEnabledSensorProto.DISABLED;
/**
+ * Constant indicating privacy is enabled except for the automotive driver assistance apps
+ * which are helpful for driving.
+ */
+ @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+ public static final int AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS =
+ SensorPrivacyIndividualEnabledSensorProto.AUTO_DRIVER_ASSISTANCE_HELPFUL_APPS;
+
+ /**
+ * Constant indicating privacy is enabled except for the automotive driver assistance apps
+ * which are required by car manufacturer for driving.
+ */
+ @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+ public static final int AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS =
+ SensorPrivacyIndividualEnabledSensorProto.AUTO_DRIVER_ASSISTANCE_REQUIRED_APPS;
+
+ /**
+ * Constant indicating privacy is enabled except for the automotive driver assistance apps
+ * which are both helpful for driving and also apps required by car manufacturer for
+ * driving.
+ */
+ @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+ public static final int AUTOMOTIVE_DRIVER_ASSISTANCE_APPS =
+ SensorPrivacyIndividualEnabledSensorProto.AUTO_DRIVER_ASSISTANCE_APPS;
+
+ /**
* Types of state which can exist for a sensor privacy toggle
*
* @hide
*/
@IntDef(value = {
ENABLED,
- DISABLED
+ DISABLED,
+ AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS,
+ AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS,
+ AUTOMOTIVE_DRIVER_ASSISTANCE_APPS
})
@Retention(RetentionPolicy.SOURCE)
public @interface StateType {}
@@ -266,6 +297,19 @@
private int mToggleType;
private int mSensor;
private boolean mEnabled;
+ private int mState;
+
+ @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+ private SensorPrivacyChangedParams(int toggleType, int sensor, int state) {
+ mToggleType = toggleType;
+ mSensor = sensor;
+ mState = state;
+ if (state == StateTypes.ENABLED) {
+ mEnabled = true;
+ } else {
+ mEnabled = false;
+ }
+ }
private SensorPrivacyChangedParams(int toggleType, int sensor, boolean enabled) {
mToggleType = toggleType;
@@ -284,6 +328,12 @@
public boolean isEnabled() {
return mEnabled;
}
+
+ @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+ public @StateTypes.StateType int getState() {
+ return mState;
+ }
+
}
}
@@ -319,6 +369,9 @@
private final ArrayMap<Pair<Integer, OnSensorPrivacyChangedListener>,
OnSensorPrivacyChangedListener> mLegacyToggleListeners = new ArrayMap<>();
+ @GuardedBy("mLock")
+ private ArrayMap<String, Boolean> mCameraPrivacyAllowlist = null;
+
/** The singleton ISensorPrivacyListener for IPC which will be used to dispatch to local
* listeners */
@NonNull
@@ -328,12 +381,33 @@
synchronized (mLock) {
for (int i = 0; i < mToggleListeners.size(); i++) {
OnSensorPrivacyChangedListener listener = mToggleListeners.keyAt(i);
- mToggleListeners.valueAt(i).execute(() -> listener
- .onSensorPrivacyChanged(new OnSensorPrivacyChangedListener
- .SensorPrivacyChangedParams(toggleType, sensor, enabled)));
+ if (Flags.privacyAllowlist()) {
+ int state = enabled ? StateTypes.ENABLED : StateTypes.DISABLED;
+ mToggleListeners.valueAt(i).execute(() -> listener
+ .onSensorPrivacyChanged(new OnSensorPrivacyChangedListener
+ .SensorPrivacyChangedParams(toggleType, sensor, state)));
+ } else {
+ mToggleListeners.valueAt(i).execute(() -> listener
+ .onSensorPrivacyChanged(new OnSensorPrivacyChangedListener
+ .SensorPrivacyChangedParams(toggleType, sensor, enabled)));
+ }
}
}
}
+
+ @Override
+ @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+ public void onSensorPrivacyStateChanged(int toggleType, int sensor, int state) {
+ synchronized (mLock) {
+ for (int i = 0; i < mToggleListeners.size(); i++) {
+ OnSensorPrivacyChangedListener listener = mToggleListeners.keyAt(i);
+ mToggleListeners.valueAt(i).execute(() -> listener
+ .onSensorPrivacyChanged(new OnSensorPrivacyChangedListener
+ .SensorPrivacyChangedParams(toggleType, sensor, state)));
+ }
+ }
+ }
+
};
/** Whether the singleton ISensorPrivacyListener has been registered */
@@ -649,6 +723,73 @@
}
/**
+ * Returns sensor privacy state for a specific sensor.
+ *
+ * @return int sensor privacy state.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY)
+ @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+ public @StateTypes.StateType int getSensorPrivacyState(@ToggleType int toggleType,
+ @Sensors.Sensor int sensor) {
+ try {
+ return mService.getToggleSensorPrivacyState(toggleType, sensor);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns if camera privacy is enabled for a specific package.
+ *
+ * @return boolean sensor privacy state.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY)
+ @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+ public boolean isCameraPrivacyEnabled(@NonNull String packageName) {
+ try {
+ return mService.isCameraPrivacyEnabled(packageName);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns camera privacy allowlist.
+ *
+ * @return List of automotive driver assistance packages for
+ * privacy allowlisting. The returned map includes the package
+ * name as key and the value is a Boolean which tells if that package
+ * is required by the car manufacturer as mandatory package for driving.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY)
+ @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+ public @NonNull Map<String, Boolean> getCameraPrivacyAllowlist() {
+ synchronized (mLock) {
+ if (mCameraPrivacyAllowlist == null) {
+ mCameraPrivacyAllowlist = new ArrayMap<>();
+ try {
+ for (CameraPrivacyAllowlistEntry entry :
+ mService.getCameraPrivacyAllowlist()) {
+ mCameraPrivacyAllowlist.put(entry.packageName, entry.isMandatory);
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return mCameraPrivacyAllowlist;
+ }
+ }
+
+ /**
* Sets sensor privacy to the specified state for an individual sensor.
*
* @param sensor the sensor which to change the state for
@@ -677,6 +818,22 @@
* Sets sensor privacy to the specified state for an individual sensor.
*
* @param sensor the sensor which to change the state for
+ * @param state the state to which sensor privacy should be set.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY)
+ @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+ public void setSensorPrivacyState(@Sensors.Sensor int sensor,
+ @StateTypes.StateType int state) {
+ setSensorPrivacyState(resolveSourceFromCurrentContext(), sensor, state);
+ }
+
+ /**
+ * 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
@@ -708,6 +865,27 @@
}
/**
+ * Sets sensor privacy to the specified state for an individual sensor.
+ *
+ * @param sensor the sensor which to change the state for
+ * @param state the state to which sensor privacy should be set.
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY)
+ @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+ public void setSensorPrivacyState(@Sources.Source int source, @Sensors.Sensor int sensor,
+ @StateTypes.StateType int state) {
+ try {
+ mService.setToggleSensorPrivacyState(mContext.getUserId(), source, sensor, state);
+ } 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.
*
@@ -745,6 +923,28 @@
}
/**
+ * 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 state the state to which sensor privacy should be set.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY)
+ @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+ public void setSensorPrivacyStateForProfileGroup(@Sources.Source int source,
+ @Sensors.Sensor int sensor, @StateTypes.StateType int state) {
+ try {
+ mService.setToggleSensorPrivacyStateForProfileGroup(mContext.getUserId(), source,
+ sensor, state);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Don't show dialogs to turn off sensor privacy for this package.
*
* @param suppress Whether to suppress or re-enable.
@@ -865,6 +1065,12 @@
boolean enabled) {
listener.onAllSensorPrivacyChanged(enabled);
}
+
+ @Override
+ @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+ public void onSensorPrivacyStateChanged(int toggleType, int sensor,
+ int state) {
+ }
};
mListeners.put(listener, iListener);
}
diff --git a/core/proto/android/hardware/sensorprivacy.proto b/core/proto/android/hardware/sensorprivacy.proto
index 9359528..e368c6a 100644
--- a/core/proto/android/hardware/sensorprivacy.proto
+++ b/core/proto/android/hardware/sensorprivacy.proto
@@ -91,6 +91,9 @@
enum StateType {
ENABLED = 1;
DISABLED = 2;
+ AUTO_DRIVER_ASSISTANCE_HELPFUL_APPS = 3;
+ AUTO_DRIVER_ASSISTANCE_REQUIRED_APPS = 4;
+ AUTO_DRIVER_ASSISTANCE_APPS = 5;
}
// DEPRECATED
@@ -134,4 +137,4 @@
// Source for which sensor privacy was toggled.
optional Source source = 1;
-}
\ No newline at end of file
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index a425bb0..316001a 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1730,6 +1730,16 @@
android:description="@string/permdesc_cameraHeadlessSystemUser"
android:protectionLevel="signature" />
+
+ <!-- @SystemApi Allows camera access of allowlisted driver assistance apps
+ to be controlled separately.
+ <p> Not for use by third-party applications.
+ @FlaggedApi("com.android.internal.camera.flags.privacy_allowlist")
+ @hide
+ -->
+ <permission android:name="android.permission.CAMERA_PRIVACY_ALLOWLIST"
+ android:protectionLevel="signature|privileged" />
+
<!-- ====================================================================== -->
<!-- Permissions for accessing the device sensors -->
<!-- ====================================================================== -->
diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
index 2f0fc51..98c0217 100644
--- a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
@@ -23,6 +23,7 @@
import android.content.DialogInterface.BUTTON_POSITIVE
import android.content.Intent
import android.content.Intent.EXTRA_PACKAGE_NAME
+import android.content.pm.PackageManager
import android.hardware.SensorPrivacyManager
import android.hardware.SensorPrivacyManager.EXTRA_ALL_SENSORS
import android.hardware.SensorPrivacyManager.EXTRA_SENSOR
@@ -31,6 +32,7 @@
import android.os.Handler
import android.window.OnBackInvokedDispatcher
import androidx.annotation.OpenForTesting
+import com.android.internal.camera.flags.Flags
import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION
import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION__ACTION__CANCEL
import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION__ACTION__ENABLE
@@ -90,14 +92,14 @@
sensor = ALL_SENSORS
val callback = IndividualSensorPrivacyController.Callback { _, _ ->
if (!sensorPrivacyController.isSensorBlocked(MICROPHONE) &&
- !sensorPrivacyController.isSensorBlocked(CAMERA)) {
+ !isCameraBlocked(sensorUsePackageName)) {
finish()
}
}
sensorPrivacyListener = callback
sensorPrivacyController.addCallback(callback)
if (!sensorPrivacyController.isSensorBlocked(MICROPHONE) &&
- !sensorPrivacyController.isSensorBlocked(CAMERA)) {
+ !isCameraBlocked(sensorUsePackageName)) {
finish()
return
}
@@ -110,14 +112,22 @@
}
val callback = IndividualSensorPrivacyController.Callback {
whichSensor: Int, isBlocked: Boolean ->
- if (whichSensor == sensor && !isBlocked) {
+ if (whichSensor != sensor) {
+ // Ignore a callback; we're not interested in.
+ } else if ((whichSensor == CAMERA) && !isCameraBlocked(sensorUsePackageName)) {
+ finish()
+ } else if ((whichSensor == MICROPHONE) && !isBlocked) {
finish()
}
}
sensorPrivacyListener = callback
sensorPrivacyController.addCallback(callback)
- if (!sensorPrivacyController.isSensorBlocked(sensor)) {
+ if ((sensor == CAMERA) && !isCameraBlocked(sensorUsePackageName)) {
+ finish()
+ return
+ } else if ((sensor == MICROPHONE) &&
+ !sensorPrivacyController.isSensorBlocked(MICROPHONE)) {
finish()
return
}
@@ -204,6 +214,22 @@
recreate()
}
+ private fun isAutomotive(): Boolean {
+ return getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
+ }
+
+ private fun isCameraBlocked(packageName: String): Boolean {
+ if (Flags.privacyAllowlist()) {
+ if (isAutomotive()) {
+ return sensorPrivacyController.isCameraPrivacyEnabled(packageName)
+ } else {
+ return sensorPrivacyController.isSensorBlocked(CAMERA)
+ }
+ } else {
+ return sensorPrivacyController.isSensorBlocked(CAMERA)
+ }
+ }
+
private fun disableSensorPrivacy() {
if (sensor == ALL_SENSORS) {
sensorPrivacyController.setSensorBlocked(DIALOG, MICROPHONE, false)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyController.java
index eb08f37..fb67358 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyController.java
@@ -16,9 +16,12 @@
package com.android.systemui.statusbar.policy;
+import android.annotation.FlaggedApi;
import android.hardware.SensorPrivacyManager.Sensors.Sensor;
import android.hardware.SensorPrivacyManager.Sources.Source;
+import com.android.internal.camera.flags.Flags;
+
public interface IndividualSensorPrivacyController extends
CallbackController<IndividualSensorPrivacyController.Callback> {
void init();
@@ -42,6 +45,12 @@
*/
boolean requiresAuthentication();
+ /**
+ * @return whether camera privacy is enabled for the package.
+ */
+ @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+ boolean isCameraPrivacyEnabled(String packageName);
+
interface Callback {
void onSensorBlockedChanged(@Sensor int sensor, boolean blocked);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java
index 87dfc99..8f768e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java
@@ -19,6 +19,9 @@
import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE;
+import android.Manifest;
+import android.annotation.FlaggedApi;
+import android.annotation.RequiresPermission;
import android.hardware.SensorPrivacyManager;
import android.hardware.SensorPrivacyManager.Sensors.Sensor;
import android.hardware.SensorPrivacyManager.Sources.Source;
@@ -28,6 +31,8 @@
import androidx.annotation.NonNull;
+import com.android.internal.camera.flags.Flags;
+
import java.util.Set;
public class IndividualSensorPrivacyControllerImpl implements IndividualSensorPrivacyController {
@@ -102,6 +107,13 @@
}
@Override
+ @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+ @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY)
+ public boolean isCameraPrivacyEnabled(String packageName) {
+ return mSensorPrivacyManager.isCameraPrivacyEnabled(packageName);
+ }
+
+ @Override
public void addCallback(@NonNull Callback listener) {
mCallbacks.add(listener);
}
diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java
index a341b4a..2e14abb 100644
--- a/services/core/java/com/android/server/SystemConfig.java
+++ b/services/core/java/com/android/server/SystemConfig.java
@@ -263,6 +263,10 @@
// location settings are off, for emergency purposes, as read from the configuration files.
final ArrayMap<String, ArraySet<String>> mAllowIgnoreLocationSettings = new ArrayMap<>();
+ // These are the packages that are allow-listed to be able to access camera when
+ // the camera privacy state is for driver assistance apps only.
+ final ArrayMap<String, Boolean> mAllowlistCameraPrivacy = new ArrayMap<>();
+
// These are the action strings of broadcasts which are whitelisted to
// be delivered anonymously even to apps which target O+.
final ArraySet<String> mAllowImplicitBroadcasts = new ArraySet<>();
@@ -483,6 +487,10 @@
return mAllowedAssociations;
}
+ public ArrayMap<String, Boolean> getCameraPrivacyAllowlist() {
+ return mAllowlistCameraPrivacy;
+ }
+
public ArraySet<String> getBugreportWhitelistedPackages() {
return mBugreportWhitelistedPackages;
}
@@ -1062,6 +1070,22 @@
}
XmlUtils.skipCurrentTag(parser);
} break;
+ case "camera-privacy-allowlisted-app" : {
+ if (allowOverrideAppRestrictions) {
+ String pkgname = parser.getAttributeValue(null, "package");
+ boolean isMandatory = XmlUtils.readBooleanAttribute(
+ parser, "mandatory", false);
+ if (pkgname == null) {
+ Slog.w(TAG, "<" + name + "> without package in "
+ + permFile + " at " + parser.getPositionDescription());
+ } else {
+ mAllowlistCameraPrivacy.put(pkgname, isMandatory);
+ }
+ } else {
+ logNotAllowedInPartition(name, permFile, parser);
+ }
+ XmlUtils.skipCurrentTag(parser);
+ } break;
case "allow-ignore-location-settings": {
if (allowOverrideAppRestrictions) {
String pkgname = parser.getAttributeValue(null, "package");
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 5c95d43..63ea7b4 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -106,6 +106,7 @@
import android.content.pm.PermissionInfo;
import android.content.pm.UserInfo;
import android.database.ContentObserver;
+import android.hardware.SensorPrivacyManager;
import android.hardware.camera2.CameraDevice.CAMERA_AUDIO_RESTRICTION;
import android.net.Uri;
import android.os.AsyncTask;
@@ -151,6 +152,7 @@
import com.android.internal.app.IAppOpsService;
import com.android.internal.app.IAppOpsStartedCallback;
import com.android.internal.app.MessageSamplingConfig;
+import com.android.internal.camera.flags.Flags;
import com.android.internal.compat.IPlatformCompat;
import com.android.internal.os.Clock;
import com.android.internal.pm.pkg.component.ParsedAttribution;
@@ -223,6 +225,8 @@
*/
private static final int CURRENT_VERSION = 1;
+ private SensorPrivacyManager mSensorPrivacyManager;
+
// Write at most every 30 minutes.
static final long WRITE_DELAY = DEBUG ? 1000 : 30*60*1000;
@@ -1231,6 +1235,7 @@
}
}
});
+ mSensorPrivacyManager = SensorPrivacyManager.getInstance(mContext);
}
@VisibleForTesting
@@ -4642,6 +4647,10 @@
return pmi.isPackageSuspended(packageName, UserHandle.getUserId(uid));
}
+ private boolean isAutomotive() {
+ return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
+ }
+
private boolean isOpRestrictedLocked(int uid, int code, String packageName,
String attributionTag, int virtualDeviceId, @Nullable RestrictionBypass appBypass,
boolean isCheckOp) {
@@ -4658,6 +4667,14 @@
}
}
+ if (Flags.privacyAllowlist()) {
+ if ((code == OP_CAMERA) && isAutomotive()) {
+ if (mSensorPrivacyManager.isCameraPrivacyEnabled(packageName)) {
+ return true;
+ }
+ }
+ }
+
int userHandle = UserHandle.getUserId(uid);
restrictionSetCount = mOpUserRestrictions.size();
diff --git a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
index 59766ec..64cfc8d4 100644
--- a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
+++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
@@ -45,6 +45,11 @@
import static android.hardware.SensorPrivacyManager.Sources.QS_TILE;
import static android.hardware.SensorPrivacyManager.Sources.SETTINGS;
import static android.hardware.SensorPrivacyManager.Sources.SHELL;
+import static android.hardware.SensorPrivacyManager.StateTypes.AUTOMOTIVE_DRIVER_ASSISTANCE_APPS;
+import static android.hardware.SensorPrivacyManager.StateTypes.AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS;
+import static android.hardware.SensorPrivacyManager.StateTypes.AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS;
+import static android.hardware.SensorPrivacyManager.StateTypes.DISABLED;
+import static android.hardware.SensorPrivacyManager.StateTypes.ENABLED;
import static android.hardware.SensorPrivacyManager.TOGGLE_TYPE_HARDWARE;
import static android.hardware.SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE;
import static android.os.UserHandle.USER_NULL;
@@ -52,6 +57,9 @@
import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION;
import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__ACTION_UNKNOWN;
+import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_APPS;
+import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS;
+import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS;
import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_OFF;
import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_ON;
import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__SENSOR__CAMERA;
@@ -63,8 +71,11 @@
import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__SOURCE__SOURCE_UNKNOWN;
import static com.android.internal.util.FrameworkStatsLog.write;
+import android.Manifest;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
@@ -87,6 +98,7 @@
import android.content.res.Configuration;
import android.database.ContentObserver;
import android.graphics.drawable.Icon;
+import android.hardware.CameraPrivacyAllowlistEntry;
import android.hardware.ISensorPrivacyListener;
import android.hardware.ISensorPrivacyManager;
import android.hardware.SensorPrivacyManager;
@@ -123,6 +135,7 @@
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.camera.flags.Flags;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.DumpUtils;
@@ -131,6 +144,7 @@
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.FgThread;
import com.android.server.LocalServices;
+import com.android.server.SystemConfig;
import com.android.server.SystemService;
import com.android.server.pm.UserManagerInternal;
@@ -139,6 +153,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
@@ -154,7 +169,24 @@
SensorPrivacyService.class.getName() + ".action.disable_sensor_privacy";
public static final int REMINDER_DIALOG_DELAY_MILLIS = 500;
-
+ @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+ private static final int ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS =
+ PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS;
+ @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+ private static final int ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS =
+ PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS;
+ @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+ private static final int ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_APPS =
+ PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_APPS;
+ @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+ private static final int ACTION__TOGGLE_ON =
+ PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_ON;
+ @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+ private static final int ACTION__TOGGLE_OFF =
+ PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_OFF;
+ @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+ private static final int ACTION__ACTION_UNKNOWN =
+ PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__ACTION_UNKNOWN;
private final Context mContext;
private final SensorPrivacyServiceImpl mSensorPrivacyServiceImpl;
private final UserManagerInternal mUserManagerInternal;
@@ -176,6 +208,9 @@
private CallStateHelper mCallStateHelper;
private KeyguardManager mKeyguardManager;
+ List<CameraPrivacyAllowlistEntry> mCameraPrivacyAllowlist =
+ new ArrayList<CameraPrivacyAllowlistEntry>();
+
private int mCurrentUser = USER_NULL;
public SensorPrivacyService(Context context) {
@@ -192,6 +227,15 @@
mPackageManagerInternal = getLocalService(PackageManagerInternal.class);
mNotificationManager = mContext.getSystemService(NotificationManager.class);
mSensorPrivacyServiceImpl = new SensorPrivacyServiceImpl();
+ ArrayMap<String, Boolean> cameraPrivacyAllowlist =
+ SystemConfig.getInstance().getCameraPrivacyAllowlist();
+
+ for (Map.Entry<String, Boolean> entry : cameraPrivacyAllowlist.entrySet()) {
+ CameraPrivacyAllowlistEntry ent = new CameraPrivacyAllowlistEntry();
+ ent.packageName = entry.getKey();
+ ent.isMandatory = entry.getValue();
+ mCameraPrivacyAllowlist.add(ent);
+ }
}
@Override
@@ -324,8 +368,15 @@
mHandler, mHandler::handleSensorPrivacyChanged);
mSensorPrivacyStateController.setSensorPrivacyListener(
mHandler,
- (toggleType, userId, sensor, state) -> mHandler.handleSensorPrivacyChanged(
- userId, toggleType, sensor, state.isEnabled()));
+ (toggleType, userId, sensor, state) -> {
+ mHandler.handleSensorPrivacyChanged(
+ userId, toggleType, sensor, state.isEnabled());
+ if (Flags.privacyAllowlist()) {
+ mHandler.handleSensorPrivacyChanged(
+ userId, toggleType, sensor, state.getState());
+ }
+ });
+
}
// If sensor privacy is enabled for a sensor, but the device doesn't support sensor privacy
@@ -400,9 +451,15 @@
* @param packageName The package name of the app using the sensor
* @param sensor The sensor that is attempting to be used
*/
+ @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY)
private void onSensorUseStarted(int uid, String packageName, int sensor) {
UserHandle user = UserHandle.of(mCurrentUser);
- if (!isCombinedToggleSensorPrivacyEnabled(sensor)) {
+
+ if (Flags.privacyAllowlist() && (sensor == CAMERA) && isAutomotive(mContext)) {
+ if (!isCameraPrivacyEnabled(packageName)) {
+ return;
+ }
+ } else if (!isCombinedToggleSensorPrivacyEnabled(sensor)) {
return;
}
@@ -727,6 +784,12 @@
== Configuration.UI_MODE_TYPE_TELEVISION;
}
+ private boolean isAutomotive(Context context) {
+ int uiMode = context.getResources().getConfiguration().uiMode;
+ return (uiMode & Configuration.UI_MODE_TYPE_MASK)
+ == Configuration.UI_MODE_TYPE_CAR;
+ }
+
/**
* Sets the sensor privacy to the provided state and notifies all listeners of the new
* state.
@@ -766,6 +829,225 @@
setToggleSensorPrivacyUnchecked(TOGGLE_TYPE_SOFTWARE, userId, source, sensor, enable);
}
+
+ @Override
+ @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+ @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY)
+ public void setToggleSensorPrivacyState(int userId, int source, int sensor, int state) {
+ if (DEBUG) {
+ Log.d(TAG, "callingUid=" + Binder.getCallingUid()
+ + " callingPid=" + Binder.getCallingPid()
+ + " setToggleSensorPrivacyState("
+ + "userId=" + userId
+ + " source=" + source
+ + " sensor=" + sensor
+ + " state=" + state
+ + ")");
+ }
+ enforceManageSensorPrivacyPermission();
+ if (userId == UserHandle.USER_CURRENT) {
+ userId = mCurrentUser;
+ }
+
+ if (!canChangeToggleSensorPrivacy(userId, sensor)) {
+ return;
+ }
+ if (!supportsSensorToggle(TOGGLE_TYPE_SOFTWARE, sensor)) {
+ // Do not enable sensor privacy if the device doesn't support it.
+ return;
+ }
+
+ setToggleSensorPrivacyStateUnchecked(TOGGLE_TYPE_SOFTWARE, userId, source, sensor,
+ state);
+ }
+
+ @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+ private void setToggleSensorPrivacyStateUnchecked(int toggleType, int userId, int source,
+ int sensor, int state) {
+ if (DEBUG) {
+ Log.d(TAG, "callingUid=" + Binder.getCallingUid()
+ + " callingPid=" + Binder.getCallingPid()
+ + " setToggleSensorPrivacyStateUnchecked("
+ + "userId=" + userId
+ + " source=" + source
+ + " sensor=" + sensor
+ + " state=" + state
+ + ")");
+ }
+ long[] lastChange = new long[1];
+ mSensorPrivacyStateController.atomic(() -> {
+ SensorState sensorState = mSensorPrivacyStateController
+ .getState(toggleType, userId, sensor);
+ lastChange[0] = sensorState.getLastChange();
+ mSensorPrivacyStateController.setState(
+ toggleType, userId, sensor, state, mHandler,
+ changeSuccessful -> {
+ if (changeSuccessful) {
+ if (userId == mUserManagerInternal.getProfileParentId(userId)) {
+ mHandler.sendMessage(PooledLambda.obtainMessage(
+ SensorPrivacyServiceImpl::logSensorPrivacyStateToggle,
+ this,
+ source, sensor, state, lastChange[0], false));
+ }
+ }
+ });
+ });
+ }
+
+ @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+ private void logSensorPrivacyStateToggle(int source, int sensor, int state,
+ long lastChange, boolean onShutDown) {
+ long logMins = Math.max(0, (getCurrentTimeMillis() - lastChange) / (1000 * 60));
+
+ int logAction = ACTION__ACTION_UNKNOWN;
+ if (!onShutDown) {
+ switch(state) {
+ case ENABLED :
+ logAction = ACTION__TOGGLE_OFF;
+ break;
+ case DISABLED :
+ logAction = ACTION__TOGGLE_ON;
+ break;
+ case AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS :
+ logAction = ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS;
+ break;
+ case AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS :
+ logAction = ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS;
+ break;
+ case AUTOMOTIVE_DRIVER_ASSISTANCE_APPS :
+ logAction = ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_APPS;
+ break;
+ default :
+ logAction = ACTION__ACTION_UNKNOWN;
+ break;
+ }
+ }
+
+ int logSensor = PRIVACY_SENSOR_TOGGLE_INTERACTION__SENSOR__SENSOR_UNKNOWN;
+ switch(sensor) {
+ case CAMERA:
+ logSensor = PRIVACY_SENSOR_TOGGLE_INTERACTION__SENSOR__CAMERA;
+ break;
+ case MICROPHONE:
+ logSensor = PRIVACY_SENSOR_TOGGLE_INTERACTION__SENSOR__MICROPHONE;
+ break;
+ default:
+ logSensor = PRIVACY_SENSOR_TOGGLE_INTERACTION__SENSOR__SENSOR_UNKNOWN;
+ break;
+ }
+
+ int logSource = PRIVACY_SENSOR_TOGGLE_INTERACTION__SOURCE__SOURCE_UNKNOWN;
+ switch(source) {
+ case QS_TILE :
+ logSource = PRIVACY_SENSOR_TOGGLE_INTERACTION__SOURCE__QS_TILE;
+ break;
+ case DIALOG :
+ logSource = PRIVACY_SENSOR_TOGGLE_INTERACTION__SOURCE__DIALOG;
+ break;
+ case SETTINGS:
+ logSource = PRIVACY_SENSOR_TOGGLE_INTERACTION__SOURCE__SETTINGS;
+ break;
+ default:
+ logSource = PRIVACY_SENSOR_TOGGLE_INTERACTION__SOURCE__SOURCE_UNKNOWN;
+ break;
+ }
+
+ if (DEBUG || DEBUG_LOGGING) {
+ Log.d(TAG, "Logging sensor toggle interaction:" + " logSensor=" + logSensor
+ + " logAction=" + logAction + " logSource=" + logSource + " logMins="
+ + logMins);
+ }
+ write(PRIVACY_SENSOR_TOGGLE_INTERACTION, logSensor, logAction, logSource, logMins);
+
+ }
+
+ @Override
+ @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+ @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY)
+ public void setToggleSensorPrivacyStateForProfileGroup(int userId, int source, int sensor,
+ int state) {
+ enforceManageSensorPrivacyPermission();
+ if (userId == UserHandle.USER_CURRENT) {
+ userId = mCurrentUser;
+ }
+ int parentId = mUserManagerInternal.getProfileParentId(userId);
+ forAllUsers(userId2 -> {
+ if (parentId == mUserManagerInternal.getProfileParentId(userId2)) {
+ setToggleSensorPrivacyState(userId2, source, sensor, state);
+ }
+ });
+ }
+
+ @Override
+ @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+ @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY)
+ public List<CameraPrivacyAllowlistEntry> getCameraPrivacyAllowlist() {
+ enforceObserveSensorPrivacyPermission();
+ return mCameraPrivacyAllowlist;
+ }
+
+ @Override
+ @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+ @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY)
+ public boolean isCameraPrivacyEnabled(String packageName) {
+ if (DEBUG) {
+ Log.d(TAG, "callingUid=" + Binder.getCallingUid()
+ + " callingPid=" + Binder.getCallingPid()
+ + " isCameraPrivacyEnabled("
+ + "packageName=" + packageName
+ + ")");
+ }
+ enforceObserveSensorPrivacyPermission();
+
+ int state = mSensorPrivacyStateController.getState(TOGGLE_TYPE_SOFTWARE, mCurrentUser,
+ CAMERA).getState();
+ if (state == ENABLED) {
+ return true;
+ } else if (state == DISABLED) {
+ return false;
+ } else if (state == AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS) {
+ for (CameraPrivacyAllowlistEntry entry : mCameraPrivacyAllowlist) {
+ if ((packageName.equals(entry.packageName)) && !entry.isMandatory) {
+ return false;
+ }
+ }
+ return true;
+ } else if (state == AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS) {
+ for (CameraPrivacyAllowlistEntry entry : mCameraPrivacyAllowlist) {
+ if ((packageName.equals(entry.packageName)) && entry.isMandatory) {
+ return false;
+ }
+ }
+ return true;
+ } else if (state == AUTOMOTIVE_DRIVER_ASSISTANCE_APPS) {
+ for (CameraPrivacyAllowlistEntry entry : mCameraPrivacyAllowlist) {
+ if (packageName.equals(entry.packageName)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+ @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY)
+ public int getToggleSensorPrivacyState(int toggleType, int sensor) {
+ if (DEBUG) {
+ Log.d(TAG, "callingUid=" + Binder.getCallingUid()
+ + " callingPid=" + Binder.getCallingPid()
+ + " getToggleSensorPrivacyState("
+ + "toggleType=" + toggleType
+ + " sensor=" + sensor
+ + ")");
+ }
+ enforceObserveSensorPrivacyPermission();
+
+ return mSensorPrivacyStateController.getState(toggleType, mCurrentUser, sensor)
+ .getState();
+ }
+
private void setToggleSensorPrivacyUnchecked(int toggleType, int userId, int source,
int sensor, boolean enable) {
if (DEBUG) {
@@ -899,16 +1181,23 @@
* Enforces the caller contains the necessary permission to change the state of sensor
* privacy.
*/
+ @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY)
private void enforceManageSensorPrivacyPermission() {
- enforcePermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY,
- "Changing sensor privacy requires the following permission: "
- + MANAGE_SENSOR_PRIVACY);
+ if (mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.MANAGE_SENSOR_PRIVACY) == PERMISSION_GRANTED) {
+ return;
+ }
+
+ String message = "Changing sensor privacy requires the following permission: "
+ + MANAGE_SENSOR_PRIVACY;
+ throw new SecurityException(message);
}
/**
* Enforces the caller contains the necessary permission to observe changes to the sate of
* sensor privacy.
*/
+ @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY)
private void enforceObserveSensorPrivacyPermission() {
String systemUIPackage = mContext.getString(R.string.config_systemUi);
int systemUIAppId = UserHandle.getAppId(mPackageManagerInternal
@@ -917,15 +1206,13 @@
// b/221782106, possible race condition with role grant might bootloop device.
return;
}
- enforcePermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY,
- "Observing sensor privacy changes requires the following permission: "
- + android.Manifest.permission.OBSERVE_SENSOR_PRIVACY);
- }
-
- private void enforcePermission(String permission, String message) {
- if (mContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED) {
+ if (mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) == PERMISSION_GRANTED) {
return;
}
+
+ String message = "Observing sensor privacy changes requires the following permission: "
+ + android.Manifest.permission.OBSERVE_SENSOR_PRIVACY;
throw new SecurityException(message);
}
@@ -1293,11 +1580,13 @@
}
@Override
+ @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY)
public void onShellCommand(FileDescriptor in, FileDescriptor out,
FileDescriptor err, String[] args, ShellCallback callback,
ResultReceiver resultReceiver) {
(new ShellCommand() {
@Override
+ @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY)
public int onCommand(String cmd) {
if (cmd == null) {
return handleDefaultCommands(cmd);
@@ -1327,6 +1616,45 @@
setToggleSensorPrivacy(userId, SHELL, sensor, false);
}
break;
+ case "automotive_driver_assistance_apps" : {
+ if (Flags.privacyAllowlist()) {
+ int sensor = sensorStrToId(getNextArgRequired());
+ if ((!isAutomotive(mContext)) || (sensor != CAMERA)) {
+ pw.println("Command not valid for this sensor");
+ return -1;
+ }
+
+ setToggleSensorPrivacyState(userId, SHELL, sensor,
+ AUTOMOTIVE_DRIVER_ASSISTANCE_APPS);
+ }
+ }
+ break;
+ case "automotive_driver_assistance_helpful_apps" : {
+ if (Flags.privacyAllowlist()) {
+ int sensor = sensorStrToId(getNextArgRequired());
+ if ((!isAutomotive(mContext)) || (sensor != CAMERA)) {
+ pw.println("Command not valid for this sensor");
+ return -1;
+ }
+
+ setToggleSensorPrivacyState(userId, SHELL, sensor,
+ AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS);
+ }
+ }
+ break;
+ case "automotive_driver_assistance_required_apps" : {
+ if (Flags.privacyAllowlist()) {
+ int sensor = sensorStrToId(getNextArgRequired());
+ if ((!isAutomotive(mContext)) || (sensor != CAMERA)) {
+ pw.println("Command not valid for this sensor");
+ return -1;
+ }
+
+ setToggleSensorPrivacyState(userId, SHELL, sensor,
+ AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS);
+ }
+ }
+ break;
default:
return handleDefaultCommands(cmd);
}
@@ -1349,6 +1677,24 @@
pw.println(" disable USER_ID SENSOR");
pw.println(" Disable privacy for a certain sensor.");
pw.println("");
+ if (Flags.privacyAllowlist()) {
+ if (isAutomotive(mContext)) {
+ pw.println(" automotive_driver_assistance_apps USER_ID SENSOR");
+ pw.println(" Disable privacy for automotive apps which help you"
+ + " drive and apps which are required by OEM");
+ pw.println("");
+ pw.println(" automotive_driver_assistance_helpful_apps "
+ + "USER_ID SENSOR");
+ pw.println(" Disable privacy for automotive apps which "
+ + "help you drive.");
+ pw.println("");
+ pw.println(" automotive_driver_assistance_required_apps "
+ + "USER_ID SENSOR");
+ pw.println(" Disable privacy for automotive apps which are "
+ + "required by OEM.");
+ pw.println("");
+ }
+ }
}
}).exec(this, in, out, err, args, callback, resultReceiver);
}
@@ -1457,6 +1803,38 @@
mSensorPrivacyServiceImpl.showSensorStateChangedActivity(sensor, toggleType);
}
+ @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+ public void handleSensorPrivacyChanged(int userId, int toggleType, int sensor,
+ int state) {
+ if (userId == mCurrentUser) {
+ mSensorPrivacyServiceImpl.setGlobalRestriction(sensor,
+ mSensorPrivacyServiceImpl.isCombinedToggleSensorPrivacyEnabled(sensor));
+ }
+
+ if (userId != mCurrentUser) {
+ return;
+ }
+ synchronized (mListenerLock) {
+ try {
+ final int count = mToggleSensorListeners.beginBroadcast();
+ for (int i = 0; i < count; i++) {
+ ISensorPrivacyListener listener = mToggleSensorListeners.getBroadcastItem(
+ i);
+ try {
+ listener.onSensorPrivacyStateChanged(toggleType, sensor, state);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Caught an exception notifying listener " + listener + ": ",
+ e);
+ }
+ }
+ } finally {
+ mToggleSensorListeners.finishBroadcast();
+ }
+ }
+
+ mSensorPrivacyServiceImpl.showSensorStateChangedActivity(sensor, toggleType);
+ }
+
public void removeSuppressPackageReminderToken(Pair<Integer, UserHandle> key,
IBinder token) {
sendMessage(PooledLambda.obtainMessage(
diff --git a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyStateController.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyStateController.java
index 9694958..0e29222 100644
--- a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyStateController.java
+++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyStateController.java
@@ -16,9 +16,11 @@
package com.android.server.sensorprivacy;
+import android.annotation.FlaggedApi;
import android.os.Handler;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.camera.flags.Flags;
import com.android.internal.util.dump.DualDumpOutputStream;
import com.android.internal.util.function.pooled.PooledLambda;
@@ -51,6 +53,14 @@
}
}
+ @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+ void setState(int toggleType, int userId, int sensor, int state, Handler callbackHandler,
+ SetStateResultCallback callback) {
+ synchronized (mLock) {
+ setStateLocked(toggleType, userId, sensor, state, callbackHandler, callback);
+ }
+ }
+
void setSensorPrivacyListener(Handler handler,
SensorPrivacyListener listener) {
synchronized (mLock) {
@@ -128,6 +138,11 @@
Handler callbackHandler, SetStateResultCallback callback);
@GuardedBy("mLock")
+ @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+ abstract void setStateLocked(int toggleType, int userId, int sensor, int state,
+ Handler callbackHandler, SetStateResultCallback callback);
+
+ @GuardedBy("mLock")
abstract void setSensorPrivacyListenerLocked(Handler handler,
SensorPrivacyListener listener);
diff --git a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyStateControllerImpl.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyStateControllerImpl.java
index 3dcb4cf..2d96aeb 100644
--- a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyStateControllerImpl.java
+++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyStateControllerImpl.java
@@ -16,8 +16,12 @@
package com.android.server.sensorprivacy;
+import static android.hardware.SensorPrivacyManager.StateTypes.DISABLED;
+
+import android.annotation.FlaggedApi;
import android.os.Handler;
+import com.android.internal.camera.flags.Flags;
import com.android.internal.util.dump.DualDumpOutputStream;
import com.android.internal.util.function.pooled.PooledLambda;
@@ -85,6 +89,33 @@
sendSetStateCallback(callbackHandler, callback, false);
}
+ @Override
+ @FlaggedApi(Flags.FLAG_PRIVACY_ALLOWLIST)
+ void setStateLocked(int toggleType, int userId, int sensor, int state,
+ Handler callbackHandler, SetStateResultCallback callback) {
+ // Changing the SensorState's mEnabled updates the timestamp of its last change.
+ // A nonexistent state -> unmuted should not set the timestamp.
+ SensorState lastState = mPersistedState.getState(toggleType, userId, sensor);
+ if (lastState == null) {
+ if (state == DISABLED) {
+ sendSetStateCallback(callbackHandler, callback, false);
+ return;
+ } else {
+ SensorState sensorState = new SensorState(state);
+ mPersistedState.setState(toggleType, userId, sensor, sensorState);
+ notifyStateChangeLocked(toggleType, userId, sensor, sensorState);
+ sendSetStateCallback(callbackHandler, callback, true);
+ return;
+ }
+ }
+ if (lastState.setState(state)) {
+ notifyStateChangeLocked(toggleType, userId, sensor, lastState);
+ sendSetStateCallback(callbackHandler, callback, true);
+ return;
+ }
+ sendSetStateCallback(callbackHandler, callback, false);
+ }
+
private void notifyStateChangeLocked(int toggleType, int userId, int sensor,
SensorState sensorState) {
if (mListenerHandler != null && mListener != null) {