From 2e3ba7cd302465754c63e164c886b58259322f9e Mon Sep 17 00:00:00 2001 From: Evan Severson Date: Wed, 9 Dec 2020 13:45:21 -0800 Subject: [PATCH 1/2] Make the sensor privacy state per user For the spaceship mode this is purely an internal refactorization since all the apis remain the same and the state is kept in sync across users. For the individual controls there's new calls to set the state of the toggle per user or per profile group. Test: Manually change state with `adb shell cmd sensor_privacy enable 0 microphone` Manually upgrade from old persistence to new persistence form Manually check that the persisted state remains when rebooted. Bug: 162549680 Change-Id: I0a5eac7a5c3beb272a456790d999a0282d77bf09 --- .../hardware/ISensorPrivacyManager.aidl | 5 +- .../hardware/SensorPrivacyManager.java | 27 +- .../android/hardware/sensorprivacy.proto | 18 + .../android/server/SensorPrivacyService.java | 350 ++++++++++++++---- 4 files changed, 325 insertions(+), 75 deletions(-) diff --git a/core/java/android/hardware/ISensorPrivacyManager.aidl b/core/java/android/hardware/ISensorPrivacyManager.aidl index 4e368d0610b36..46682adcb147e 100644 --- a/core/java/android/hardware/ISensorPrivacyManager.aidl +++ b/core/java/android/hardware/ISensorPrivacyManager.aidl @@ -34,10 +34,11 @@ interface ISensorPrivacyManager { // =============== End of transactions used on native side as well ============================ // TODO(evanseverson) add to native interface - boolean isIndividualSensorPrivacyEnabled(int sensor); + boolean isIndividualSensorPrivacyEnabled(int userId, int sensor); // TODO(evanseverson) add to native interface - void setIndividualSensorPrivacy(int sensor, boolean enable); + void setIndividualSensorPrivacy(int userId, int sensor, boolean enable); + void setIndividualSensorPrivacyForProfileGroup(int userId, int sensor, boolean enable); // TODO(evanseverson) listeners } \ No newline at end of file diff --git a/core/java/android/hardware/SensorPrivacyManager.java b/core/java/android/hardware/SensorPrivacyManager.java index c647239d90497..4d488715b2c51 100644 --- a/core/java/android/hardware/SensorPrivacyManager.java +++ b/core/java/android/hardware/SensorPrivacyManager.java @@ -20,6 +20,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SystemService; +import android.annotation.UserIdInt; import android.content.Context; import android.os.IBinder; import android.os.RemoteException; @@ -198,9 +199,10 @@ public final class SensorPrivacyManager { * * @return true if sensor privacy is currently enabled, false otherwise. */ - public boolean isIndividualSensorPrivacyEnabled(@IndividualSensor int sensor) { + public boolean isIndividualSensorPrivacyEnabled(@UserIdInt int userId, + @IndividualSensor int sensor) { try { - return mService.isIndividualSensorPrivacyEnabled(sensor); + return mService.isIndividualSensorPrivacyEnabled(userId, sensor); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -212,9 +214,26 @@ public final class SensorPrivacyManager { * @param enable the state to which sensor privacy should be set. */ @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) - public void setIndividualSensorPrivacy(@IndividualSensor int sensor, boolean enable) { + public void setIndividualSensorPrivacy(@UserIdInt int userId, @IndividualSensor int sensor, + boolean enable) { try { - mService.setIndividualSensorPrivacy(sensor, enable); + mService.setIndividualSensorPrivacy(userId, sensor, enable); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Sets sensor privacy to the specified state for an individual sensor for the profile group of + * the given user. + * + * @param enable the state to which sensor privacy should be set. + */ + @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) + public void setIndividualSensorPrivacyForProfileGroup(@UserIdInt int userId, + @IndividualSensor int sensor, boolean enable) { + try { + mService.setIndividualSensorPrivacyForProfileGroup(userId, sensor, enable); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/proto/android/hardware/sensorprivacy.proto b/core/proto/android/hardware/sensorprivacy.proto index 07e938ddfc5d6..401e0038cada3 100644 --- a/core/proto/android/hardware/sensorprivacy.proto +++ b/core/proto/android/hardware/sensorprivacy.proto @@ -25,11 +25,29 @@ import "frameworks/base/core/proto/android/privacy.proto"; message SensorPrivacyServiceDumpProto { option (android.msg_privacy).dest = DEST_AUTOMATIC; + // DEPRECATED // Is global sensor privacy enabled optional bool is_enabled = 1; + // DEPRECATED // Per sensor privacy enabled repeated SensorPrivacyIndividualEnabledSensorProto individual_enabled_sensor = 2; + + // Per user settings for sensor privacy + repeated SensorPrivacyUserProto user = 3; +} + +message SensorPrivacyUserProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + // User id + optional int32 user_id = 1; + + // Is global sensor privacy enabled + optional bool is_enabled = 2; + + // Per sensor privacy enabled + repeated SensorPrivacyIndividualEnabledSensorProto individual_enabled_sensor = 3; } message SensorPrivacyIndividualEnabledSensorProto { diff --git a/services/core/java/com/android/server/SensorPrivacyService.java b/services/core/java/com/android/server/SensorPrivacyService.java index 010213453940e..2b705f5ffb3e5 100644 --- a/services/core/java/com/android/server/SensorPrivacyService.java +++ b/services/core/java/com/android/server/SensorPrivacyService.java @@ -20,12 +20,14 @@ import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.OP_CAMERA; import static android.app.AppOpsManager.OP_RECORD_AUDIO; import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.os.UserHandle.USER_SYSTEM; import static android.service.SensorPrivacyIndividualEnabledSensorProto.CAMERA; import static android.service.SensorPrivacyIndividualEnabledSensorProto.MICROPHONE; import static android.service.SensorPrivacyIndividualEnabledSensorProto.UNKNOWN; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UserIdInt; import android.app.AppOpsManager; import android.app.Notification; import android.app.NotificationChannel; @@ -49,12 +51,15 @@ import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ShellCallback; import android.os.ShellCommand; +import android.os.UserHandle; import android.service.SensorPrivacyIndividualEnabledSensorProto; import android.service.SensorPrivacyServiceDumpProto; +import android.service.SensorPrivacyUserProto; import android.util.ArrayMap; import android.util.AtomicFile; import android.util.IndentingPrintWriter; import android.util.Log; +import android.util.SparseArray; import android.util.SparseBooleanArray; import android.util.TypedXmlPullParser; import android.util.TypedXmlSerializer; @@ -63,9 +68,11 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.DumpUtils; +import com.android.internal.util.FunctionalUtils; import com.android.internal.util.XmlUtils; import com.android.internal.util.dump.DualDumpOutputStream; import com.android.internal.util.function.pooled.PooledLambda; +import com.android.server.pm.UserManagerInternal; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -84,9 +91,19 @@ public final class SensorPrivacyService extends SystemService { private static final String TAG = "SensorPrivacyService"; + /** Version number indicating compatibility parsing the persisted file */ + private static final int CURRENT_PERSISTENCE_VERSION = 1; + /** Version number indicating the persisted data needs upgraded to match new internal data + * structures and features */ + private static final int CURRENT_VERSION = 1; + private static final String SENSOR_PRIVACY_XML_FILE = "sensor_privacy.xml"; private static final String XML_TAG_SENSOR_PRIVACY = "sensor-privacy"; + private static final String XML_TAG_USER = "user"; private static final String XML_TAG_INDIVIDUAL_SENSOR_PRIVACY = "individual-sensor-privacy"; + private static final String XML_ATTRIBUTE_ID = "id"; + private static final String XML_ATTRIBUTE_PERSISTENCE_VERSION = "persistence-version"; + private static final String XML_ATTRIBUTE_VERSION = "version"; private static final String XML_ATTRIBUTE_ENABLED = "enabled"; private static final String XML_ATTRIBUTE_SENSOR = "sensor"; @@ -96,10 +113,18 @@ public final class SensorPrivacyService extends SystemService { private static final String EXTRA_SENSOR = SensorPrivacyService.class.getName() + ".extra.sensor"; + // These are associated with fields that existed for older persisted versions of files + private static final int VER0_ENABLED = 0; + private static final int VER0_INDIVIDUAL_ENABLED = 1; + private static final int VER1_ENABLED = 0; + private static final int VER1_INDIVIDUAL_ENABLED = 1; + private final SensorPrivacyServiceImpl mSensorPrivacyServiceImpl; + private final UserManagerInternal mUserManagerInternal; public SensorPrivacyService(Context context) { super(context); + mUserManagerInternal = getLocalService(UserManagerInternal.class); mSensorPrivacyServiceImpl = new SensorPrivacyServiceImpl(context); } @@ -117,8 +142,9 @@ public final class SensorPrivacyService extends SystemService { @GuardedBy("mLock") private final AtomicFile mAtomicFile; @GuardedBy("mLock") - private boolean mEnabled; - private SparseBooleanArray mIndividualEnabled = new SparseBooleanArray(); + private SparseBooleanArray mEnabled = new SparseBooleanArray(); + @GuardedBy("mLock") + private SparseArray mIndividualEnabled = new SparseArray<>(); SensorPrivacyServiceImpl(Context context) { mContext = context; @@ -127,7 +153,9 @@ public final class SensorPrivacyService extends SystemService { SENSOR_PRIVACY_XML_FILE); mAtomicFile = new AtomicFile(sensorPrivacyFile); synchronized (mLock) { - readPersistedSensorPrivacyStateLocked(); + if (readPersistedSensorPrivacyStateLocked()) { + persistSensorPrivacyStateLocked(); + } } int[] micAndCameraOps = new int[]{OP_RECORD_AUDIO, OP_CAMERA}; @@ -138,7 +166,8 @@ public final class SensorPrivacyService extends SystemService { mContext.registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - setIndividualSensorPrivacy(intent.getIntExtra(EXTRA_SENSOR, UNKNOWN), false); + setIndividualSensorPrivacy(intent.getIntExtra(Intent.EXTRA_USER_ID, -1), + intent.getIntExtra(EXTRA_SENSOR, UNKNOWN), false); } }, new IntentFilter(ACTION_DISABLE_INDIVIDUAL_SENSOR_PRIVACY)); } @@ -174,7 +203,8 @@ public final class SensorPrivacyService extends SystemService { * @param sensor The sensor that is attempting to be used */ private void onSensorUseStarted(int uid, String packageName, int sensor) { - if (!isIndividualSensorPrivacyEnabled(sensor)) { + int userId = UserHandle.getUserId(uid); + if (!isIndividualSensorPrivacyEnabled(userId, sensor)) { return; } @@ -216,7 +246,8 @@ public final class SensorPrivacyService extends SystemService { PendingIntent.getBroadcast(mContext, sensor, new Intent(ACTION_DISABLE_INDIVIDUAL_SENSOR_PRIVACY) .setPackage(mContext.getPackageName()) - .putExtra(EXTRA_SENSOR, sensor), + .putExtra(EXTRA_SENSOR, sensor) + .putExtra(Intent.EXTRA_USER_ID, userId), PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT)) .build()) @@ -229,18 +260,27 @@ public final class SensorPrivacyService extends SystemService { */ @Override public void setSensorPrivacy(boolean enable) { + // Keep the state consistent between all users to make it a single global state + forAllUsers(userId -> setSensorPrivacy(userId, enable)); + } + + private void setSensorPrivacy(@UserIdInt int userId, boolean enable) { enforceSensorPrivacyPermission(); synchronized (mLock) { - mEnabled = enable; + mEnabled.put(userId, enable); persistSensorPrivacyStateLocked(); } mHandler.onSensorPrivacyChanged(enable); } - public void setIndividualSensorPrivacy(int sensor, boolean enable) { + @Override + public void setIndividualSensorPrivacy(@UserIdInt int userId, int sensor, boolean enable) { enforceSensorPrivacyPermission(); synchronized (mLock) { - mIndividualEnabled.put(sensor, enable); + SparseBooleanArray userIndividualEnabled = mIndividualEnabled.get(userId, + new SparseBooleanArray()); + userIndividualEnabled.put(sensor, enable); + mIndividualEnabled.put(userId, userIndividualEnabled); if (!enable) { // Remove any notifications prompting the user to disable sensory privacy @@ -249,11 +289,21 @@ public final class SensorPrivacyService extends SystemService { notificationManager.cancel(sensor); } - persistSensorPrivacyState(); } } + @Override + public void setIndividualSensorPrivacyForProfileGroup(@UserIdInt int userId, int sensor, + boolean enable) { + int parentId = mUserManagerInternal.getProfileParentId(userId); + forAllUsers(userId2 -> { + if (parentId == mUserManagerInternal.getProfileParentId(userId2)) { + setIndividualSensorPrivacy(userId2, sensor, enable); + } + }); + } + /** * Enforces the caller contains the necessary permission to change the state of sensor * privacy. @@ -273,53 +323,179 @@ public final class SensorPrivacyService extends SystemService { */ @Override public boolean isSensorPrivacyEnabled() { + return isSensorPrivacyEnabled(USER_SYSTEM); + } + + private boolean isSensorPrivacyEnabled(@UserIdInt int userId) { synchronized (mLock) { - return mEnabled; + return mEnabled.get(userId, false); } } @Override - public boolean isIndividualSensorPrivacyEnabled(int sensor) { + public boolean isIndividualSensorPrivacyEnabled(@UserIdInt int userId, int sensor) { synchronized (mLock) { - return mIndividualEnabled.get(sensor, false); + SparseBooleanArray states = mIndividualEnabled.get(userId); + if (states == null) { + return false; + } + return states.get(sensor, false); } } /** * Returns the state of sensor privacy from persistent storage. */ - private void readPersistedSensorPrivacyStateLocked() { + private boolean readPersistedSensorPrivacyStateLocked() { // if the file does not exist then sensor privacy has not yet been enabled on // the device. - if (!mAtomicFile.exists()) { - return; - } - try (FileInputStream inputStream = mAtomicFile.openRead()) { - TypedXmlPullParser parser = Xml.resolvePullParser(inputStream); - XmlUtils.beginDocument(parser, XML_TAG_SENSOR_PRIVACY); - parser.next(); - mEnabled = parser.getAttributeBoolean(null, XML_ATTRIBUTE_ENABLED, false); - XmlUtils.nextElement(parser); - while (parser.getEventType() != XmlPullParser.END_DOCUMENT) { - String tagName = parser.getName(); - if (XML_TAG_INDIVIDUAL_SENSOR_PRIVACY.equals(tagName)) { - int sensor = XmlUtils.readIntAttribute(parser, XML_ATTRIBUTE_SENSOR); - boolean enabled = XmlUtils.readBooleanAttribute(parser, - XML_ATTRIBUTE_ENABLED); - mIndividualEnabled.put(sensor, enabled); - XmlUtils.skipCurrentTag(parser); - } else { + SparseArray map = new SparseArray<>(); + int version = -1; + + if (mAtomicFile.exists()) { + try (FileInputStream inputStream = mAtomicFile.openRead()) { + TypedXmlPullParser parser = Xml.resolvePullParser(inputStream); + XmlUtils.beginDocument(parser, XML_TAG_SENSOR_PRIVACY); + final int persistenceVersion = parser.getAttributeInt(null, + XML_ATTRIBUTE_PERSISTENCE_VERSION, 0); + + // Use inline string literals for xml tags/attrs when parsing old versions since + // these should never be changed even with refactorings. + if (persistenceVersion == 0) { + boolean enabled = parser.getAttributeBoolean(null, "enabled", false); + SparseBooleanArray individualEnabled = new SparseBooleanArray(); + version = 0; + XmlUtils.nextElement(parser); + while (parser.getEventType() != XmlPullParser.END_DOCUMENT) { + String tagName = parser.getName(); + if ("individual-sensor-privacy".equals(tagName)) { + int sensor = XmlUtils.readIntAttribute(parser, "sensor"); + boolean indEnabled = XmlUtils.readBooleanAttribute(parser, + "enabled"); + individualEnabled.put(sensor, indEnabled); + XmlUtils.skipCurrentTag(parser); + } else { + XmlUtils.nextElement(parser); + } + } + map.put(VER0_ENABLED, enabled); + map.put(VER0_INDIVIDUAL_ENABLED, individualEnabled); + } else if (persistenceVersion == CURRENT_PERSISTENCE_VERSION) { + SparseBooleanArray enabled = new SparseBooleanArray(); + SparseArray individualEnabled = new SparseArray<>(); + version = parser.getAttributeInt(null, + XML_ATTRIBUTE_VERSION, 1); + + int currentUserId = -1; + while (parser.getEventType() != XmlPullParser.END_DOCUMENT) { + XmlUtils.nextElement(parser); + String tagName = parser.getName(); + if (XML_TAG_USER.equals(tagName)) { + currentUserId = parser.getAttributeInt(null, XML_ATTRIBUTE_ID); + boolean isEnabled = parser.getAttributeBoolean(null, + XML_ATTRIBUTE_ENABLED); + if (enabled.indexOfKey(currentUserId) >= 0) { + Log.e(TAG, "User listed multiple times in file.", + new RuntimeException()); + mAtomicFile.delete(); + version = -1; + break; + } + + if (mUserManagerInternal.getUserInfo(currentUserId) == null) { + // User may no longer exist, skip this user + currentUserId = -1; + continue; + } + + enabled.put(currentUserId, isEnabled); + } + if (XML_TAG_INDIVIDUAL_SENSOR_PRIVACY.equals(tagName)) { + if (mUserManagerInternal.getUserInfo(currentUserId) == null) { + // User may no longer exist or isn't set + continue; + } + int sensor = parser.getAttributeIndex(null, XML_ATTRIBUTE_SENSOR); + boolean isEnabled = parser.getAttributeBoolean(null, + XML_ATTRIBUTE_ENABLED); + SparseBooleanArray userIndividualEnabled = individualEnabled.get( + currentUserId, new SparseBooleanArray()); + + userIndividualEnabled.put(sensor, isEnabled); + individualEnabled.put(currentUserId, userIndividualEnabled); + } + } + + map.put(VER1_ENABLED, enabled); + map.put(VER1_INDIVIDUAL_ENABLED, individualEnabled); + } else { + Log.e(TAG, "Unknown persistence version: " + persistenceVersion + + ". Deleting.", + new RuntimeException()); + mAtomicFile.delete(); + version = -1; + } + + } catch (IOException | XmlPullParserException e) { + Log.e(TAG, "Caught an exception reading the state from storage: ", e); + // Delete the file to prevent the same error on subsequent calls and assume + // sensor privacy is not enabled. + mAtomicFile.delete(); + version = -1; + } + } + + return upgradeAndInit(version, map); + } + + private boolean upgradeAndInit(int version, SparseArray map) { + if (version == -1) { + // New file, default state for current version goes here. + mEnabled = new SparseBooleanArray(); + mIndividualEnabled = new SparseArray<>(); + forAllUsers(userId -> mEnabled.put(userId, false)); + forAllUsers(userId -> mIndividualEnabled.put(userId, new SparseBooleanArray())); + return true; + } + boolean upgraded = false; + final int[] users = getLocalService(UserManagerInternal.class).getUserIds(); + if (version == 0) { + final boolean enabled = (boolean) map.get(VER0_ENABLED); + final SparseBooleanArray individualEnabled = + (SparseBooleanArray) map.get(VER0_INDIVIDUAL_ENABLED); + + final SparseBooleanArray perUserEnabled = new SparseBooleanArray(); + final SparseArray perUserIndividualEnabled = + new SparseArray<>(); + + // Copy global state to each user + for (int i = 0; i < users.length; i++) { + int user = users[i]; + perUserEnabled.put(user, enabled); + SparseBooleanArray userIndividualSensorEnabled = new SparseBooleanArray(); + perUserIndividualEnabled.put(user, userIndividualSensorEnabled); + for (int j = 0; j < individualEnabled.size(); j++) { + final int sensor = individualEnabled.keyAt(j); + final boolean isSensorEnabled = individualEnabled.valueAt(j); + userIndividualSensorEnabled.put(sensor, isSensorEnabled); } } - } catch (IOException | XmlPullParserException e) { - Log.e(TAG, "Caught an exception reading the state from storage: ", e); - // Delete the file to prevent the same error on subsequent calls and assume sensor - // privacy is not enabled. - mAtomicFile.delete(); + map.clear(); + map.put(VER1_ENABLED, perUserEnabled); + map.put(VER1_INDIVIDUAL_ENABLED, perUserIndividualEnabled); + + version = 1; + upgraded = true; } + if (version == CURRENT_VERSION) { + mEnabled = (SparseBooleanArray) map.get(VER1_ENABLED); + mIndividualEnabled = + (SparseArray) map.get(VER1_INDIVIDUAL_ENABLED); + } + return upgraded; } /** @@ -338,16 +514,29 @@ public final class SensorPrivacyService extends SystemService { TypedXmlSerializer serializer = Xml.resolveSerializer(outputStream); serializer.startDocument(null, true); serializer.startTag(null, XML_TAG_SENSOR_PRIVACY); - serializer.attributeBoolean(null, XML_ATTRIBUTE_ENABLED, mEnabled); - int numIndividual = mIndividualEnabled.size(); - for (int i = 0; i < numIndividual; i++) { - serializer.startTag(null, XML_TAG_INDIVIDUAL_SENSOR_PRIVACY); - int sensor = mIndividualEnabled.keyAt(i); - boolean enabled = mIndividualEnabled.valueAt(i); - serializer.attributeInt(null, XML_ATTRIBUTE_SENSOR, sensor); - serializer.attributeBoolean(null, XML_ATTRIBUTE_ENABLED, enabled); - serializer.endTag(null, XML_TAG_INDIVIDUAL_SENSOR_PRIVACY); - } + serializer.attributeInt( + null, XML_ATTRIBUTE_PERSISTENCE_VERSION, CURRENT_PERSISTENCE_VERSION); + serializer.attributeInt(null, XML_ATTRIBUTE_VERSION, CURRENT_VERSION); + forAllUsers(userId -> { + serializer.startTag(null, XML_TAG_USER); + serializer.attributeInt(null, XML_ATTRIBUTE_ID, userId); + serializer.attributeBoolean( + null, XML_ATTRIBUTE_ENABLED, isSensorPrivacyEnabled(userId)); + + SparseBooleanArray individualEnabled = + mIndividualEnabled.get(userId, new SparseBooleanArray()); + int numIndividual = individualEnabled.size(); + for (int i = 0; i < numIndividual; i++) { + serializer.startTag(null, XML_TAG_INDIVIDUAL_SENSOR_PRIVACY); + int sensor = individualEnabled.keyAt(i); + boolean enabled = individualEnabled.valueAt(i); + serializer.attributeInt(null, XML_ATTRIBUTE_SENSOR, sensor); + serializer.attributeBoolean(null, XML_ATTRIBUTE_ENABLED, enabled); + serializer.endTag(null, XML_TAG_INDIVIDUAL_SENSOR_PRIVACY); + } + serializer.endTag(null, XML_TAG_USER); + + }); serializer.endTag(null, XML_TAG_SENSOR_PRIVACY); serializer.endDocument(); mAtomicFile.finishWrite(outputStream); @@ -422,22 +611,32 @@ public final class SensorPrivacyService extends SystemService { */ private void dump(@NonNull DualDumpOutputStream dumpStream) { synchronized (mLock) { - dumpStream.write("is_enabled", SensorPrivacyServiceDumpProto.IS_ENABLED, mEnabled); - int numIndividualEnabled = mIndividualEnabled.size(); - for (int i = 0; i < numIndividualEnabled; i++) { - long token = dumpStream.start("individual_enabled_sensor", - SensorPrivacyServiceDumpProto.INDIVIDUAL_ENABLED_SENSOR); + forAllUsers(userId -> { + long userToken = dumpStream.start("users", SensorPrivacyServiceDumpProto.USER); + dumpStream.write("user_id", SensorPrivacyUserProto.USER_ID, userId); + dumpStream.write("is_enabled", SensorPrivacyUserProto.IS_ENABLED, + mEnabled.get(userId, false)); - dumpStream.write("sensor", - SensorPrivacyIndividualEnabledSensorProto.SENSOR, - mIndividualEnabled.keyAt(i)); - dumpStream.write("is_enabled", - SensorPrivacyIndividualEnabledSensorProto.IS_ENABLED, - mIndividualEnabled.valueAt(i)); + SparseBooleanArray individualEnabled = mIndividualEnabled.get(userId); + if (individualEnabled != null) { + int numIndividualEnabled = individualEnabled.size(); + for (int i = 0; i < numIndividualEnabled; i++) { + long individualToken = dumpStream.start("individual_enabled_sensor", + SensorPrivacyUserProto.INDIVIDUAL_ENABLED_SENSOR); - dumpStream.end(token); - } + dumpStream.write("sensor", + SensorPrivacyIndividualEnabledSensorProto.SENSOR, + individualEnabled.keyAt(i)); + dumpStream.write("is_enabled", + SensorPrivacyIndividualEnabledSensorProto.IS_ENABLED, + individualEnabled.valueAt(i)); + + dumpStream.end(individualToken); + } + } + dumpStream.end(userToken); + }); } dumpStream.flush(); @@ -477,30 +676,32 @@ public final class SensorPrivacyService extends SystemService { return handleDefaultCommands(cmd); } + int userId = Integer.parseInt(getNextArgRequired()); + final PrintWriter pw = getOutPrintWriter(); switch (cmd) { case "enable" : { - int sensor = sensorStrToId(getNextArg()); + int sensor = sensorStrToId(getNextArgRequired()); if (sensor == UNKNOWN) { pw.println("Invalid sensor"); return -1; } - setIndividualSensorPrivacy(sensor, true); + setIndividualSensorPrivacy(userId, sensor, true); } break; case "disable" : { - int sensor = sensorStrToId(getNextArg()); + int sensor = sensorStrToId(getNextArgRequired()); if (sensor == UNKNOWN) { pw.println("Invalid sensor"); return -1; } - setIndividualSensorPrivacy(sensor, false); + setIndividualSensorPrivacy(userId, sensor, false); } break; case "reset": { - int sensor = sensorStrToId(getNextArg()); + int sensor = sensorStrToId(getNextArgRequired()); if (sensor == UNKNOWN) { pw.println("Invalid sensor"); return -1; @@ -509,7 +710,11 @@ public final class SensorPrivacyService extends SystemService { enforceSensorPrivacyPermission(); synchronized (mLock) { - mIndividualEnabled.delete(sensor); + SparseBooleanArray individualEnabled = + mIndividualEnabled.get(userId); + if (individualEnabled != null) { + individualEnabled.delete(sensor); + } persistSensorPrivacyState(); } } @@ -530,13 +735,13 @@ public final class SensorPrivacyService extends SystemService { pw.println(" help"); pw.println(" Print this help text."); pw.println(""); - pw.println(" enable SENSOR"); + pw.println(" enable USER_ID SENSOR"); pw.println(" Enable privacy for a certain sensor."); pw.println(""); - pw.println(" disable SENSOR"); + pw.println(" disable USER_ID SENSOR"); pw.println(" Disable privacy for a certain sensor."); pw.println(""); - pw.println(" reset SENSOR"); + pw.println(" reset USER_ID SENSOR"); pw.println(" Reset privacy state for a certain sensor."); pw.println(""); } @@ -628,4 +833,11 @@ public final class SensorPrivacyService extends SystemService { } } } + + private void forAllUsers(FunctionalUtils.ThrowingConsumer c) { + int[] userIds = mUserManagerInternal.getUserIds(); + for (int i = 0; i < userIds.length; i++) { + c.accept(userIds[i]); + } + } } From 1f1e8d72abd478c8b1d930f71521ddf68935d99a Mon Sep 17 00:00:00 2001 From: Evan Severson Date: Tue, 22 Dec 2020 16:12:55 -0800 Subject: [PATCH 2/2] Add listeners for individual camera and microphone toggles Test: Manually add a listener in another serviec and change state with `adb shell cmd sensor_privacy enable 0 microphone`. Bug: 162549680 Change-Id: I4a77bc2bba4405b2ba962e767005ca443a2d732f --- .../hardware/ISensorPrivacyManager.aidl | 16 ++--- .../hardware/SensorPrivacyManager.java | 31 ++++++++ .../android/server/SensorPrivacyService.java | 71 +++++++++++++++++++ 3 files changed, 110 insertions(+), 8 deletions(-) diff --git a/core/java/android/hardware/ISensorPrivacyManager.aidl b/core/java/android/hardware/ISensorPrivacyManager.aidl index 46682adcb147e..a54e88f2d4931 100644 --- a/core/java/android/hardware/ISensorPrivacyManager.aidl +++ b/core/java/android/hardware/ISensorPrivacyManager.aidl @@ -26,19 +26,19 @@ interface ISensorPrivacyManager { // =============== Beginning of transactions used on native side as well ====================== void addSensorPrivacyListener(in ISensorPrivacyListener listener); + void addIndividualSensorPrivacyListener(int userId, int sensor, + in ISensorPrivacyListener listener); + void removeSensorPrivacyListener(in ISensorPrivacyListener listener); boolean isSensorPrivacyEnabled(); - void setSensorPrivacy(boolean enable); - // =============== End of transactions used on native side as well ============================ - - // TODO(evanseverson) add to native interface boolean isIndividualSensorPrivacyEnabled(int userId, int sensor); - // TODO(evanseverson) add to native interface - void setIndividualSensorPrivacy(int userId, int sensor, boolean enable); - void setIndividualSensorPrivacyForProfileGroup(int userId, int sensor, boolean enable); + void setSensorPrivacy(boolean enable); - // TODO(evanseverson) listeners + void setIndividualSensorPrivacy(int userId, int sensor, boolean enable); + + void setIndividualSensorPrivacyForProfileGroup(int userId, int sensor, boolean enable); + // =============== End of transactions used on native side as well ============================ } \ No newline at end of file diff --git a/core/java/android/hardware/SensorPrivacyManager.java b/core/java/android/hardware/SensorPrivacyManager.java index 4d488715b2c51..b3b2dd85a8d8b 100644 --- a/core/java/android/hardware/SensorPrivacyManager.java +++ b/core/java/android/hardware/SensorPrivacyManager.java @@ -160,6 +160,37 @@ public final class SensorPrivacyManager { } } + /** + * Registers a new listener to receive notification when the state of sensor privacy + * changes. + * + * @param userId the user's id + * @param sensor the sensor to listen to changes to + * @param listener the OnSensorPrivacyChangedListener to be notified when the state of sensor + * privacy changes. + */ + public void addSensorPrivacyListener(@UserIdInt int userId, @IndividualSensor int sensor, + final OnSensorPrivacyChangedListener listener) { + synchronized (mListeners) { + ISensorPrivacyListener iListener = mListeners.get(listener); + if (iListener == null) { + iListener = new ISensorPrivacyListener.Stub() { + @Override + public void onSensorPrivacyChanged(boolean enabled) { + listener.onSensorPrivacyChanged(enabled); + } + }; + mListeners.put(listener, iListener); + } + + try { + mService.addIndividualSensorPrivacyListener(userId, sensor, iListener); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + /** * Unregisters the specified listener from receiving notifications when the state of sensor * privacy changes. diff --git a/services/core/java/com/android/server/SensorPrivacyService.java b/services/core/java/com/android/server/SensorPrivacyService.java index 2b705f5ffb3e5..9ba71dc5f4f7b 100644 --- a/services/core/java/com/android/server/SensorPrivacyService.java +++ b/services/core/java/com/android/server/SensorPrivacyService.java @@ -291,6 +291,7 @@ public final class SensorPrivacyService extends SystemService { } persistSensorPrivacyState(); } + mHandler.onSensorPrivacyChanged(userId, sensor, enable); } @Override @@ -557,6 +558,18 @@ public final class SensorPrivacyService extends SystemService { mHandler.addListener(listener); } + /** + * Registers a listener to be notified when the sensor privacy state changes. + */ + @Override + public void addIndividualSensorPrivacyListener(int userId, int sensor, + ISensorPrivacyListener listener) { + if (listener == null) { + throw new NullPointerException("listener cannot be null"); + } + mHandler.addListener(userId, sensor, listener); + } + /** * Unregisters a listener from sensor privacy state change notifications. */ @@ -760,6 +773,9 @@ public final class SensorPrivacyService extends SystemService { @GuardedBy("mListenerLock") private final RemoteCallbackList mListeners = new RemoteCallbackList<>(); + @GuardedBy("mListenerLock") + private final SparseArray>> + mIndividualSensorListeners = new SparseArray<>(); private final ArrayMap mDeathRecipients; private final Context mContext; @@ -777,6 +793,14 @@ public final class SensorPrivacyService extends SystemService { mSensorPrivacyServiceImpl)); } + public void onSensorPrivacyChanged(int userId, int sensor, boolean enabled) { + sendMessage(PooledLambda.obtainMessage(SensorPrivacyHandler::handleSensorPrivacyChanged, + this, userId, sensor, enabled)); + sendMessage( + PooledLambda.obtainMessage(SensorPrivacyServiceImpl::persistSensorPrivacyState, + mSensorPrivacyServiceImpl)); + } + public void addListener(ISensorPrivacyListener listener) { synchronized (mListenerLock) { DeathRecipient deathRecipient = new DeathRecipient(listener); @@ -785,6 +809,25 @@ public final class SensorPrivacyService extends SystemService { } } + public void addListener(int userId, int sensor, ISensorPrivacyListener listener) { + synchronized (mListenerLock) { + DeathRecipient deathRecipient = new DeathRecipient(listener); + mDeathRecipients.put(listener, deathRecipient); + SparseArray> listenersForUser = + mIndividualSensorListeners.get(userId); + if (listenersForUser == null) { + listenersForUser = new SparseArray<>(); + mIndividualSensorListeners.put(userId, listenersForUser); + } + RemoteCallbackList listeners = listenersForUser.get(sensor); + if (listeners == null) { + listeners = new RemoteCallbackList<>(); + listenersForUser.put(sensor, listeners); + } + listeners.register(listener); + } + } + public void removeListener(ISensorPrivacyListener listener) { synchronized (mListenerLock) { DeathRecipient deathRecipient = mDeathRecipients.remove(listener); @@ -792,6 +835,12 @@ public final class SensorPrivacyService extends SystemService { deathRecipient.destroy(); } mListeners.unregister(listener); + for (int i = 0, numUsers = mIndividualSensorListeners.size(); i < numUsers; i++) { + for (int j = 0, numListeners = mIndividualSensorListeners.valueAt(i).size(); + j < numListeners; j++) { + mIndividualSensorListeners.valueAt(i).valueAt(j).unregister(listener); + } + } } } @@ -807,6 +856,28 @@ public final class SensorPrivacyService extends SystemService { } mListeners.finishBroadcast(); } + + public void handleSensorPrivacyChanged(int userId, int sensor, boolean enabled) { + SparseArray> listenersForUser = + mIndividualSensorListeners.get(userId); + if (listenersForUser == null) { + return; + } + RemoteCallbackList listeners = listenersForUser.get(sensor); + if (listeners == null) { + return; + } + final int count = listeners.beginBroadcast(); + for (int i = 0; i < count; i++) { + ISensorPrivacyListener listener = listeners.getBroadcastItem(i); + try { + listener.onSensorPrivacyChanged(enabled); + } catch (RemoteException e) { + Log.e(TAG, "Caught an exception notifying listener " + listener + ": ", e); + } + } + listeners.finishBroadcast(); + } } private final class DeathRecipient implements IBinder.DeathRecipient {