diff --git a/core/java/android/hardware/ISensorPrivacyManager.aidl b/core/java/android/hardware/ISensorPrivacyManager.aidl index 4e368d0610b36..a54e88f2d4931 100644 --- a/core/java/android/hardware/ISensorPrivacyManager.aidl +++ b/core/java/android/hardware/ISensorPrivacyManager.aidl @@ -26,18 +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(); + boolean isIndividualSensorPrivacyEnabled(int userId, int sensor); + void setSensorPrivacy(boolean enable); + + 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 ============================ - - // TODO(evanseverson) add to native interface - boolean isIndividualSensorPrivacyEnabled(int sensor); - - // TODO(evanseverson) add to native interface - void setIndividualSensorPrivacy(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..b3b2dd85a8d8b 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; @@ -159,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. @@ -198,9 +230,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 +245,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..9ba71dc5f4f7b 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,9 +289,20 @@ public final class SensorPrivacyService extends SystemService { notificationManager.cancel(sensor); } - persistSensorPrivacyState(); } + mHandler.onSensorPrivacyChanged(userId, sensor, enable); + } + + @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); + } + }); } /** @@ -273,53 +324,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 +515,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); @@ -368,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. */ @@ -422,22 +624,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 +689,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 +723,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 +748,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(""); } @@ -555,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; @@ -572,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); @@ -580,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); @@ -587,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); + } + } } } @@ -602,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 { @@ -628,4 +904,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]); + } + } }