Merge changes from topic "CameraMicToggleListeners"
* changes: Add listeners for individual camera and microphone toggles Make the sensor privacy state per user
This commit is contained in:
committed by
Android (Google) Code Review
commit
6c25f003b8
@@ -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
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<SparseBooleanArray> 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<Object> 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<SparseBooleanArray> 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<SparseBooleanArray> 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<SparseBooleanArray>) 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<ISensorPrivacyListener> mListeners =
|
||||
new RemoteCallbackList<>();
|
||||
@GuardedBy("mListenerLock")
|
||||
private final SparseArray<SparseArray<RemoteCallbackList<ISensorPrivacyListener>>>
|
||||
mIndividualSensorListeners = new SparseArray<>();
|
||||
private final ArrayMap<ISensorPrivacyListener, DeathRecipient> 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<RemoteCallbackList<ISensorPrivacyListener>> listenersForUser =
|
||||
mIndividualSensorListeners.get(userId);
|
||||
if (listenersForUser == null) {
|
||||
listenersForUser = new SparseArray<>();
|
||||
mIndividualSensorListeners.put(userId, listenersForUser);
|
||||
}
|
||||
RemoteCallbackList<ISensorPrivacyListener> 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<RemoteCallbackList<ISensorPrivacyListener>> listenersForUser =
|
||||
mIndividualSensorListeners.get(userId);
|
||||
if (listenersForUser == null) {
|
||||
return;
|
||||
}
|
||||
RemoteCallbackList<ISensorPrivacyListener> 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<Integer> c) {
|
||||
int[] userIds = mUserManagerInternal.getUserIds();
|
||||
for (int i = 0; i < userIds.length; i++) {
|
||||
c.accept(userIds[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user