Refactor SensorPrivacyService to separate state from service

This refactoring will let us have alternative implementations for the
state of microphone/camera. It also seperates the storage of the other
feature that is known as the "Sensor mute" so that the reuse of this
service doesn't get too convoluted.

Also extract the persitable state from the state implementation so that
alternative implementations can resuse the persistence code.

Some smaller changes:
* Start the sensor use dailog as user 0 always since system UI requires
user 0
* Use SystemClock.elapsedRealTime() instead of network time since it's
guaranteed to my monotonic. This will also change that intervals used in
telemetry aren't going to measure time the device is powered off.

Ignore-AOSP-First: New OWNERS file
Test: atest CtsSensorPrivacyTestCases SensorPrivacyServiceMockingTest
Bug: 200649586
Change-Id: I4c23c2509d7d1bdf49360fc9bbadb79ccd451dcc
This commit is contained in:
Evan Severson
2021-10-06 08:28:51 -07:00
parent b628834dc3
commit 0cefec2d4f
15 changed files with 1532 additions and 654 deletions

View File

@@ -31,6 +31,7 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.service.SensorPrivacyIndividualEnabledSensorProto;
import android.service.SensorPrivacySensorProto;
import android.service.SensorPrivacyToggleSourceProto;
import android.util.ArrayMap;
import android.util.Log;
@@ -75,7 +76,7 @@ public final class SensorPrivacyManager {
private final SparseArray<Boolean> mToggleSupportCache = new SparseArray<>();
/**
* Individual sensors not listed in {@link Sensors}
* Sensor constants which are used in {@link SensorPrivacyManager}
*/
public static class Sensors {
@@ -84,12 +85,12 @@ public final class SensorPrivacyManager {
/**
* Constant for the microphone
*/
public static final int MICROPHONE = SensorPrivacyIndividualEnabledSensorProto.MICROPHONE;
public static final int MICROPHONE = SensorPrivacySensorProto.MICROPHONE;
/**
* Constant for the camera
*/
public static final int CAMERA = SensorPrivacyIndividualEnabledSensorProto.CAMERA;
public static final int CAMERA = SensorPrivacySensorProto.CAMERA;
/**
* Individual sensors not listed in {@link Sensors}
@@ -160,6 +161,68 @@ public final class SensorPrivacyManager {
}
/**
* Types of toggles which can exist for sensor privacy
* @hide
*/
public static class ToggleTypes {
private ToggleTypes() {}
/**
* Constant for software toggle.
*/
public static final int SOFTWARE = SensorPrivacyIndividualEnabledSensorProto.SOFTWARE;
/**
* Constant for hardware toggle.
*/
public static final int HARDWARE = SensorPrivacyIndividualEnabledSensorProto.HARDWARE;
/**
* Types of toggles which can exist for sensor privacy
*
* @hide
*/
@IntDef(value = {
SOFTWARE,
HARDWARE
})
@Retention(RetentionPolicy.SOURCE)
public @interface ToggleType {}
}
/**
* Types of state which can exist for the sensor privacy toggle
* @hide
*/
public static class StateTypes {
private StateTypes() {}
/**
* Constant indicating privacy is enabled.
*/
public static final int ENABLED = SensorPrivacyIndividualEnabledSensorProto.ENABLED;
/**
* Constant indicating privacy is disabled.
*/
public static final int DISABLED = SensorPrivacyIndividualEnabledSensorProto.DISABLED;
/**
* Types of state which can exist for a sensor privacy toggle
*
* @hide
*/
@IntDef(value = {
ENABLED,
DISABLED
})
@Retention(RetentionPolicy.SOURCE)
public @interface StateType {}
}
/**
* A class implementing this interface can register with the {@link
* android.hardware.SensorPrivacyManager} to receive notification when the sensor privacy
@@ -507,7 +570,6 @@ public final class SensorPrivacyManager {
/**
* Don't show dialogs to turn off sensor privacy for this package.
*
* @param packageName Package name not to show dialogs for
* @param suppress Whether to suppress or re-enable.
*
* @hide
@@ -521,7 +583,6 @@ public final class SensorPrivacyManager {
/**
* Don't show dialogs to turn off sensor privacy for this package.
*
* @param packageName Package name not to show dialogs for
* @param suppress Whether to suppress or re-enable.
* @param userId the user's id
*

View File

@@ -22,6 +22,13 @@ option java_outer_classname = "SensorPrivacyServiceProto";
import "frameworks/base/core/proto/android/privacy.proto";
message AllSensorPrivacyServiceDumpProto {
option (android.msg_privacy).dest = DEST_AUTOMATIC;
// Is global sensor privacy enabled
optional bool is_enabled = 1;
}
message SensorPrivacyServiceDumpProto {
option (android.msg_privacy).dest = DEST_AUTOMATIC;
@@ -35,6 +42,9 @@ message SensorPrivacyServiceDumpProto {
// Per user settings for sensor privacy
repeated SensorPrivacyUserProto user = 3;
// Implementation
optional string storage_implementation = 4;
}
message SensorPrivacyUserProto {
@@ -43,16 +53,47 @@ message SensorPrivacyUserProto {
// User id
optional int32 user_id = 1;
// DEPRECATED
// Is global sensor privacy enabled
optional bool is_enabled = 2;
// Per sensor privacy enabled
// DEPRECATED
repeated SensorPrivacyIndividualEnabledSensorProto individual_enabled_sensor = 3;
// Per toggle type sensor privacy
repeated SensorPrivacySensorProto sensors = 4;
}
message SensorPrivacySensorProto {
option (android.msg_privacy).dest = DEST_AUTOMATIC;
enum Sensor {
UNKNOWN = 0;
MICROPHONE = 1;
CAMERA = 2;
}
optional int32 sensor = 1;
repeated SensorPrivacyIndividualEnabledSensorProto toggles = 2;
}
message SensorPrivacyIndividualEnabledSensorProto {
option (android.msg_privacy).dest = DEST_AUTOMATIC;
enum ToggleType {
SOFTWARE = 1;
HARDWARE = 2;
}
enum StateType {
ENABLED = 1;
DISABLED = 2;
}
// DEPRECATED
enum Sensor {
UNKNOWN = 0;
@@ -63,8 +104,17 @@ message SensorPrivacyIndividualEnabledSensorProto {
// Sensor for which privacy might be enabled
optional Sensor sensor = 1;
// If sensor privacy is enabled for this sensor
// DEPRECATED
optional bool is_enabled = 2;
// Timestamp of the last time the sensor was changed
optional int64 last_change = 3;
// The toggle type for this state
optional ToggleType toggle_type = 4;
// If sensor privacy state for this sensor
optional StateType state_type = 5;
}
message SensorPrivacyToggleSourceProto {

View File

@@ -10,9 +10,6 @@ per-file ZramWriteback.java = minchan@google.com, rajekumar@google.com
# Userspace reboot
per-file UserspaceRebootLogger.java = ioffe@google.com, dvander@google.com
# Sensor Privacy
per-file SensorPrivacyService.java = file:platform/frameworks/native:/libs/sensorprivacy/OWNERS
# ServiceWatcher
per-file ServiceWatcher.java = sooniln@google.com

View File

@@ -0,0 +1,155 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.sensorprivacy;
import android.annotation.NonNull;
import android.os.Environment;
import android.os.Handler;
import android.util.AtomicFile;
import android.util.Log;
import android.util.TypedXmlPullParser;
import android.util.TypedXmlSerializer;
import android.util.Xml;
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.IoThread;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Objects;
class AllSensorStateController {
private static final String LOG_TAG = AllSensorStateController.class.getSimpleName();
private static final String SENSOR_PRIVACY_XML_FILE = "sensor_privacy.xml";
private static final String XML_TAG_SENSOR_PRIVACY = "all-sensor-privacy";
private static final String XML_TAG_SENSOR_PRIVACY_LEGACY = "sensor-privacy";
private static final String XML_ATTRIBUTE_ENABLED = "enabled";
private static AllSensorStateController sInstance;
private final AtomicFile mAtomicFile =
new AtomicFile(new File(Environment.getDataSystemDirectory(), SENSOR_PRIVACY_XML_FILE));
private boolean mEnabled;
private SensorPrivacyStateController.AllSensorPrivacyListener mListener;
private Handler mListenerHandler;
static AllSensorStateController getInstance() {
if (sInstance == null) {
sInstance = new AllSensorStateController();
}
return sInstance;
}
private AllSensorStateController() {
if (!mAtomicFile.exists()) {
return;
}
try (FileInputStream inputStream = mAtomicFile.openRead()) {
TypedXmlPullParser parser = Xml.resolvePullParser(inputStream);
while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
String tagName = parser.getName();
if (XML_TAG_SENSOR_PRIVACY.equals(tagName)) {
mEnabled |= XmlUtils
.readBooleanAttribute(parser, XML_ATTRIBUTE_ENABLED, false);
break;
}
if (XML_TAG_SENSOR_PRIVACY_LEGACY.equals(tagName)) {
mEnabled |= XmlUtils
.readBooleanAttribute(parser, XML_ATTRIBUTE_ENABLED, false);
}
if ("user".equals(tagName)) { // Migrate from mic/cam toggles format
int user = XmlUtils.readIntAttribute(parser, "id", -1);
if (user == 0) {
mEnabled |=
XmlUtils.readBooleanAttribute(parser, XML_ATTRIBUTE_ENABLED);
}
}
XmlUtils.nextElement(parser);
}
} catch (IOException | XmlPullParserException e) {
Log.e(LOG_TAG, "Caught an exception reading the state from storage: ", e);
mEnabled = false;
}
}
public boolean getAllSensorStateLocked() {
return mEnabled;
}
public void setAllSensorStateLocked(boolean enabled) {
if (mEnabled != enabled) {
mEnabled = enabled;
if (mListener != null && mListenerHandler != null) {
mListenerHandler.sendMessage(
PooledLambda.obtainMessage(mListener::onAllSensorPrivacyChanged, enabled));
}
}
}
void setAllSensorPrivacyListenerLocked(Handler handler,
SensorPrivacyStateController.AllSensorPrivacyListener listener) {
Objects.requireNonNull(handler);
Objects.requireNonNull(listener);
if (mListener != null) {
throw new IllegalStateException("Listener is already set");
}
mListener = listener;
mListenerHandler = handler;
}
public void schedulePersistLocked() {
IoThread.getHandler().sendMessage(PooledLambda.obtainMessage(this::persist, mEnabled));
}
private void persist(boolean enabled) {
FileOutputStream outputStream = null;
try {
outputStream = mAtomicFile.startWrite();
TypedXmlSerializer serializer = Xml.resolveSerializer(outputStream);
serializer.startDocument(null, true);
serializer.startTag(null, XML_TAG_SENSOR_PRIVACY);
serializer.attributeBoolean(null, XML_ATTRIBUTE_ENABLED, enabled);
serializer.endTag(null, XML_TAG_SENSOR_PRIVACY);
serializer.endDocument();
mAtomicFile.finishWrite(outputStream);
} catch (IOException e) {
Log.e(LOG_TAG, "Caught an exception persisting the sensor privacy state: ", e);
mAtomicFile.failWrite(outputStream);
}
}
void resetForTesting() {
mListener = null;
mListenerHandler = null;
mEnabled = false;
}
void dumpLocked(@NonNull DualDumpOutputStream dumpStream) {
// TODO stub
}
}

View File

@@ -0,0 +1 @@
include platform/frameworks/native:/libs/sensorprivacy/OWNERS

View File

@@ -0,0 +1,494 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.sensorprivacy;
import android.hardware.SensorPrivacyManager;
import android.os.Environment;
import android.os.UserHandle;
import android.service.SensorPrivacyIndividualEnabledSensorProto;
import android.service.SensorPrivacySensorProto;
import android.service.SensorPrivacyServiceDumpProto;
import android.service.SensorPrivacyUserProto;
import android.util.ArrayMap;
import android.util.AtomicFile;
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
import android.util.TypedXmlPullParser;
import android.util.TypedXmlSerializer;
import android.util.Xml;
import com.android.internal.util.XmlUtils;
import com.android.internal.util.dump.DualDumpOutputStream;
import com.android.internal.util.function.QuadConsumer;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.IoThread;
import com.android.server.LocalServices;
import com.android.server.pm.UserManagerInternal;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* Class for managing persisted state. Synchronization must be handled by the caller.
*/
class PersistedState {
private static final String LOG_TAG = PersistedState.class.getSimpleName();
/** Version number indicating compatibility parsing the persisted file */
private static final int CURRENT_PERSISTENCE_VERSION = 2;
/** Version number indicating the persisted data needs upgraded to match new internal data
* structures and features */
private static final int CURRENT_VERSION = 2;
private static final String XML_TAG_SENSOR_PRIVACY = "sensor-privacy";
private static final String XML_TAG_SENSOR_STATE = "sensor-state";
private static final String XML_ATTRIBUTE_PERSISTENCE_VERSION = "persistence-version";
private static final String XML_ATTRIBUTE_VERSION = "version";
private static final String XML_ATTRIBUTE_TOGGLE_TYPE = "toggle-type";
private static final String XML_ATTRIBUTE_USER_ID = "user-id";
private static final String XML_ATTRIBUTE_SENSOR = "sensor";
private static final String XML_ATTRIBUTE_STATE_TYPE = "state-type";
private static final String XML_ATTRIBUTE_LAST_CHANGE = "last-change";
private final AtomicFile mAtomicFile;
private ArrayMap<TypeUserSensor, SensorState> mStates = new ArrayMap<>();
static PersistedState fromFile(String fileName) {
return new PersistedState(fileName);
}
private PersistedState(String fileName) {
mAtomicFile = new AtomicFile(new File(Environment.getDataSystemDirectory(), fileName));
readState();
}
private void readState() {
AtomicFile file = mAtomicFile;
if (!file.exists()) {
AtomicFile fileToMigrateFrom =
new AtomicFile(new File(Environment.getDataSystemDirectory(),
"sensor_privacy.xml"));
if (fileToMigrateFrom.exists()) {
// Sample the start tag to determine if migration is needed
try (FileInputStream inputStream = fileToMigrateFrom.openRead()) {
TypedXmlPullParser parser = Xml.resolvePullParser(inputStream);
XmlUtils.beginDocument(parser, XML_TAG_SENSOR_PRIVACY);
file = fileToMigrateFrom;
} catch (IOException e) {
Log.e(LOG_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.
fileToMigrateFrom.delete();
} catch (XmlPullParserException e) {
// No migration needed
}
}
}
Object nonupgradedState = null;
if (file.exists()) {
try (FileInputStream inputStream = file.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) {
int version = 0;
PVersion0 version0 = new PVersion0(version);
nonupgradedState = version0;
readPVersion0(parser, version0);
} else if (persistenceVersion == 1) {
int version = parser.getAttributeInt(null,
"version", 1);
PVersion1 version1 = new PVersion1(version);
nonupgradedState = version1;
readPVersion1(parser, version1);
} else if (persistenceVersion == CURRENT_PERSISTENCE_VERSION) {
int version = parser.getAttributeInt(null,
XML_ATTRIBUTE_VERSION, 2);
PVersion2 version2 = new PVersion2(version);
nonupgradedState = version2;
readPVersion2(parser, version2);
} else {
Log.e(LOG_TAG, "Unknown persistence version: " + persistenceVersion
+ ". Deleting.",
new RuntimeException());
file.delete();
nonupgradedState = null;
}
} catch (IOException | XmlPullParserException | RuntimeException e) {
Log.e(LOG_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.
file.delete();
nonupgradedState = null;
}
}
if (nonupgradedState == null) {
// New file, default state for current version goes here.
nonupgradedState = new PVersion2(2);
}
if (nonupgradedState instanceof PVersion0) {
nonupgradedState = PVersion1.fromPVersion0((PVersion0) nonupgradedState);
}
if (nonupgradedState instanceof PVersion1) {
nonupgradedState = PVersion2.fromPVersion1((PVersion1) nonupgradedState);
}
if (nonupgradedState instanceof PVersion2) {
PVersion2 upgradedState = (PVersion2) nonupgradedState;
mStates = upgradedState.mStates;
} else {
Log.e(LOG_TAG, "State not successfully upgraded.");
mStates = new ArrayMap<>();
}
}
private static void readPVersion0(TypedXmlPullParser parser, PVersion0 version0)
throws XmlPullParserException, IOException {
XmlUtils.nextElement(parser);
while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
if ("individual-sensor-privacy".equals(parser.getName())) {
int sensor = XmlUtils.readIntAttribute(parser, "sensor");
boolean indEnabled = XmlUtils.readBooleanAttribute(parser,
"enabled");
version0.addState(sensor, indEnabled);
XmlUtils.skipCurrentTag(parser);
} else {
XmlUtils.nextElement(parser);
}
}
}
private static void readPVersion1(TypedXmlPullParser parser, PVersion1 version1)
throws XmlPullParserException, IOException {
while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
XmlUtils.nextElement(parser);
if ("user".equals(parser.getName())) {
int currentUserId = parser.getAttributeInt(null, "id");
int depth = parser.getDepth();
while (XmlUtils.nextElementWithin(parser, depth)) {
if ("individual-sensor-privacy".equals(parser.getName())) {
int sensor = parser.getAttributeInt(null, "sensor");
boolean isEnabled = parser.getAttributeBoolean(null,
"enabled");
version1.addState(currentUserId, sensor, isEnabled);
}
}
}
}
}
private static void readPVersion2(TypedXmlPullParser parser, PVersion2 version2)
throws XmlPullParserException, IOException {
while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
XmlUtils.nextElement(parser);
if (XML_TAG_SENSOR_STATE.equals(parser.getName())) {
int toggleType = parser.getAttributeInt(null, XML_ATTRIBUTE_TOGGLE_TYPE);
int userId = parser.getAttributeInt(null, XML_ATTRIBUTE_USER_ID);
int sensor = parser.getAttributeInt(null, XML_ATTRIBUTE_SENSOR);
int state = parser.getAttributeInt(null, XML_ATTRIBUTE_STATE_TYPE);
long lastChange = parser.getAttributeLong(null, XML_ATTRIBUTE_LAST_CHANGE);
version2.addState(toggleType, userId, sensor, state, lastChange);
} else {
XmlUtils.skipCurrentTag(parser);
}
}
}
public SensorState getState(int toggleType, int userId, int sensor) {
return mStates.get(new TypeUserSensor(toggleType, userId, sensor));
}
public SensorState setState(int toggleType, int userId, int sensor, SensorState sensorState) {
return mStates.put(new TypeUserSensor(toggleType, userId, sensor), sensorState);
}
private static class TypeUserSensor {
int mType;
int mUserId;
int mSensor;
TypeUserSensor(int type, int userId, int sensor) {
mType = type;
mUserId = userId;
mSensor = sensor;
}
TypeUserSensor(TypeUserSensor typeUserSensor) {
this(typeUserSensor.mType, typeUserSensor.mUserId, typeUserSensor.mSensor);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof TypeUserSensor)) return false;
TypeUserSensor that = (TypeUserSensor) o;
return mType == that.mType && mUserId == that.mUserId && mSensor == that.mSensor;
}
@Override
public int hashCode() {
return 31 * (31 * mType + mUserId) + mSensor;
}
}
void schedulePersist() {
int numStates = mStates.size();
ArrayMap<TypeUserSensor, SensorState> statesCopy = new ArrayMap<>();
for (int i = 0; i < numStates; i++) {
statesCopy.put(new TypeUserSensor(mStates.keyAt(i)),
new SensorState(mStates.valueAt(i)));
}
IoThread.getHandler().sendMessage(
PooledLambda.obtainMessage(PersistedState::persist, this, statesCopy));
}
private void persist(ArrayMap<TypeUserSensor, SensorState> states) {
FileOutputStream outputStream = null;
try {
outputStream = mAtomicFile.startWrite();
TypedXmlSerializer serializer = Xml.resolveSerializer(outputStream);
serializer.startDocument(null, true);
serializer.startTag(null, XML_TAG_SENSOR_PRIVACY);
serializer.attributeInt(null, XML_ATTRIBUTE_PERSISTENCE_VERSION,
CURRENT_PERSISTENCE_VERSION);
serializer.attributeInt(null, XML_ATTRIBUTE_VERSION, CURRENT_VERSION);
for (int i = 0; i < states.size(); i++) {
TypeUserSensor userSensor = states.keyAt(i);
SensorState sensorState = states.valueAt(i);
serializer.startTag(null, XML_TAG_SENSOR_STATE);
serializer.attributeInt(null, XML_ATTRIBUTE_TOGGLE_TYPE,
userSensor.mType);
serializer.attributeInt(null, XML_ATTRIBUTE_USER_ID,
userSensor.mUserId);
serializer.attributeInt(null, XML_ATTRIBUTE_SENSOR,
userSensor.mSensor);
serializer.attributeInt(null, XML_ATTRIBUTE_STATE_TYPE,
sensorState.getState());
serializer.attributeLong(null, XML_ATTRIBUTE_LAST_CHANGE,
sensorState.getLastChange());
serializer.endTag(null, XML_TAG_SENSOR_STATE);
}
serializer.endTag(null, XML_TAG_SENSOR_PRIVACY);
serializer.endDocument();
mAtomicFile.finishWrite(outputStream);
} catch (IOException e) {
Log.e(LOG_TAG, "Caught an exception persisting the sensor privacy state: ", e);
mAtomicFile.failWrite(outputStream);
}
}
void dump(DualDumpOutputStream dumpStream) {
// Collect per user, then per sensor. <toggle type, state>
SparseArray<SparseArray<Pair<Integer, SensorState>>> statesMatrix = new SparseArray<>();
int numStates = mStates.size();
for (int i = 0; i < numStates; i++) {
int toggleType = mStates.keyAt(i).mType;
int userId = mStates.keyAt(i).mUserId;
int sensor = mStates.keyAt(i).mSensor;
SparseArray<Pair<Integer, SensorState>> userStates = statesMatrix.get(userId);
if (userStates == null) {
userStates = new SparseArray<>();
statesMatrix.put(userId, userStates);
}
userStates.put(sensor, new Pair<>(toggleType, mStates.valueAt(i)));
}
dumpStream.write("storage_implementation",
SensorPrivacyServiceDumpProto.STORAGE_IMPLEMENTATION,
SensorPrivacyStateControllerImpl.class.getName());
int numUsers = statesMatrix.size();
for (int i = 0; i < numUsers; i++) {
int userId = statesMatrix.keyAt(i);
long userToken = dumpStream.start("users", SensorPrivacyServiceDumpProto.USER);
dumpStream.write("user_id", SensorPrivacyUserProto.USER_ID, userId);
SparseArray<Pair<Integer, SensorState>> userStates = statesMatrix.valueAt(i);
int numSensors = userStates.size();
for (int j = 0; j < numSensors; j++) {
int sensor = userStates.keyAt(j);
int toggleType = userStates.valueAt(j).first;
SensorState sensorState = userStates.valueAt(j).second;
long sensorToken = dumpStream.start("sensors", SensorPrivacyUserProto.SENSORS);
dumpStream.write("sensor", SensorPrivacySensorProto.SENSOR, sensor);
long toggleToken = dumpStream.start("toggles", SensorPrivacySensorProto.TOGGLES);
dumpStream.write("toggle_type",
SensorPrivacyIndividualEnabledSensorProto.TOGGLE_TYPE,
toggleType);
dumpStream.write("state_type",
SensorPrivacyIndividualEnabledSensorProto.STATE_TYPE,
sensorState.getState());
dumpStream.write("last_change",
SensorPrivacyIndividualEnabledSensorProto.LAST_CHANGE,
sensorState.getLastChange());
dumpStream.end(toggleToken);
dumpStream.end(sensorToken);
}
dumpStream.end(userToken);
}
}
void forEachKnownState(QuadConsumer<Integer, Integer, Integer, SensorState> consumer) {
int numStates = mStates.size();
for (int i = 0; i < numStates; i++) {
TypeUserSensor tus = mStates.keyAt(i);
SensorState sensorState = mStates.valueAt(i);
consumer.accept(tus.mType, tus.mUserId, tus.mSensor, sensorState);
}
}
// Structure for persistence version 0
private static class PVersion0 {
private SparseArray<SensorState> mIndividualEnabled = new SparseArray<>();
private PVersion0(int version) {
if (version != 0) {
throw new RuntimeException("Only version 0 supported");
}
}
private void addState(int sensor, boolean enabled) {
mIndividualEnabled.put(sensor, new SensorState(enabled));
}
private void upgrade() {
// No op, only version 0 is supported
}
}
// Structure for persistence version 1
private static class PVersion1 {
private SparseArray<SparseArray<SensorState>> mIndividualEnabled = new SparseArray<>();
private PVersion1(int version) {
if (version != 1) {
throw new RuntimeException("Only version 1 supported");
}
}
private static PVersion1 fromPVersion0(PVersion0 version0) {
version0.upgrade();
PVersion1 result = new PVersion1(1);
int[] users = {UserHandle.USER_SYSTEM};
try {
users = LocalServices.getService(UserManagerInternal.class).getUserIds();
} catch (Exception e) {
Log.e(LOG_TAG, "Unable to get users.", e);
}
// Copy global state to each user
for (int i = 0; i < users.length; i++) {
int userId = users[i];
for (int j = 0; j < version0.mIndividualEnabled.size(); j++) {
final int sensor = version0.mIndividualEnabled.keyAt(j);
final SensorState sensorState = version0.mIndividualEnabled.valueAt(j);
result.addState(userId, sensor, sensorState.isEnabled());
}
}
return result;
}
private void addState(int userId, int sensor, boolean enabled) {
SparseArray<SensorState> userIndividualSensorEnabled =
mIndividualEnabled.get(userId, new SparseArray<>());
mIndividualEnabled.put(userId, userIndividualSensorEnabled);
userIndividualSensorEnabled
.put(sensor, new SensorState(enabled));
}
private void upgrade() {
// No op, only version 1 is supported
}
}
// Structure for persistence version 2
private static class PVersion2 {
private ArrayMap<TypeUserSensor, SensorState> mStates = new ArrayMap<>();
private PVersion2(int version) {
if (version != 2) {
throw new RuntimeException("Only version 2 supported");
}
}
private static PVersion2 fromPVersion1(PVersion1 version1) {
version1.upgrade();
PVersion2 result = new PVersion2(2);
SparseArray<SparseArray<SensorState>> individualEnabled =
version1.mIndividualEnabled;
int numUsers = individualEnabled.size();
for (int i = 0; i < numUsers; i++) {
int userId = individualEnabled.keyAt(i);
SparseArray<SensorState> userIndividualEnabled = individualEnabled.valueAt(i);
int numSensors = userIndividualEnabled.size();
for (int j = 0; j < numSensors; j++) {
int sensor = userIndividualEnabled.keyAt(j);
SensorState sensorState = userIndividualEnabled.valueAt(j);
result.addState(SensorPrivacyManager.ToggleTypes.SOFTWARE,
userId, sensor, sensorState.getState(), sensorState.getLastChange());
}
}
return result;
}
private void addState(int toggleType, int userId, int sensor, int state,
long lastChange) {
mStates.put(new TypeUserSensor(toggleType, userId, sensor),
new SensorState(state, lastChange));
}
}
public void resetForTesting() {
mStates = new ArrayMap<>();
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.android.server;
package com.android.server.sensorprivacy;
import static android.Manifest.permission.MANAGE_SENSOR_PRIVACY;
import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_CAMERA;
@@ -40,10 +40,10 @@ import static android.hardware.SensorPrivacyManager.Sources.QS_TILE;
import static android.hardware.SensorPrivacyManager.Sources.SETTINGS;
import static android.hardware.SensorPrivacyManager.Sources.SHELL;
import static android.os.UserHandle.USER_NULL;
import static android.os.UserHandle.USER_SYSTEM;
import static android.service.SensorPrivacyIndividualEnabledSensorProto.UNKNOWN;
import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION;
import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__ACTION_UNKNOWN;
import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_OFF;
import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_ON;
import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__SENSOR__CAMERA;
@@ -83,7 +83,6 @@ import android.hardware.SensorPrivacyManager;
import android.hardware.SensorPrivacyManagerInternal;
import android.os.Binder;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -97,26 +96,19 @@ import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.service.SensorPrivacyIndividualEnabledSensorProto;
import android.service.SensorPrivacyServiceDumpProto;
import android.service.SensorPrivacyUserProto;
import android.service.voice.VoiceInteractionManagerInternal;
import android.telephony.TelephonyCallback;
import android.telephony.TelephonyManager;
import android.telephony.emergency.EmergencyNumber;
import android.text.Html;
import android.text.Spanned;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.TypedXmlPullParser;
import android.util.TypedXmlSerializer;
import android.util.Xml;
import android.util.proto.ProtoOutputStream;
import com.android.internal.R;
@@ -125,19 +117,14 @@ import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.os.BackgroundThread;
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.FgThread;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.pm.UserManagerInternal;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
@@ -151,32 +138,10 @@ public final class SensorPrivacyService extends SystemService {
private static final boolean DEBUG = false;
private static final boolean DEBUG_LOGGING = false;
/** 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_LAST_CHANGE = "last-change";
private static final String XML_ATTRIBUTE_SENSOR = "sensor";
private static final String SENSOR_PRIVACY_CHANNEL_ID = Context.SENSOR_PRIVACY_SERVICE;
private static final String ACTION_DISABLE_INDIVIDUAL_SENSOR_PRIVACY =
SensorPrivacyService.class.getName() + ".action.disable_sensor_privacy";
// 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;
public static final int REMINDER_DIALOG_DELAY_MILLIS = 500;
private final Context mContext;
@@ -200,6 +165,7 @@ public final class SensorPrivacyService extends SystemService {
public SensorPrivacyService(Context context) {
super(context);
mContext = context;
mAppOpsManager = context.getSystemService(AppOpsManager.class);
mAppOpsManagerInternal = getLocalService(AppOpsManagerInternal.class);
@@ -247,12 +213,8 @@ public final class SensorPrivacyService extends SystemService {
private final SensorPrivacyHandler mHandler;
private final Object mLock = new Object();
@GuardedBy("mLock")
private final AtomicFile mAtomicFile;
@GuardedBy("mLock")
private SparseBooleanArray mEnabled = new SparseBooleanArray();
@GuardedBy("mLock")
private SparseArray<SparseArray<SensorState>> mIndividualEnabled = new SparseArray<>();
private SensorPrivacyStateController mSensorPrivacyStateController;
/**
* Packages for which not to show sensor use reminders.
@@ -266,34 +228,6 @@ public final class SensorPrivacyService extends SystemService {
private final ArrayMap<SensorUseReminderDialogInfo, ArraySet<Integer>>
mQueuedSensorUseReminderDialogs = new ArrayMap<>();
private class SensorState {
private boolean mEnabled;
private long mLastChange;
SensorState(boolean enabled) {
mEnabled = enabled;
mLastChange = getCurrentTimeMillis();
}
SensorState(boolean enabled, long lastChange) {
mEnabled = enabled;
if (lastChange < 0) {
mLastChange = getCurrentTimeMillis();
} else {
mLastChange = lastChange;
}
}
boolean setEnabled(boolean enabled) {
if (mEnabled != enabled) {
mEnabled = enabled;
mLastChange = getCurrentTimeMillis();
return true;
}
return false;
}
}
private class SensorUseReminderDialogInfo {
private int mTaskId;
private UserHandle mUser;
@@ -323,14 +257,7 @@ public final class SensorPrivacyService extends SystemService {
SensorPrivacyServiceImpl() {
mHandler = new SensorPrivacyHandler(FgThread.get().getLooper(), mContext);
File sensorPrivacyFile = new File(Environment.getDataSystemDirectory(),
SENSOR_PRIVACY_XML_FILE);
mAtomicFile = new AtomicFile(sensorPrivacyFile);
synchronized (mLock) {
if (readPersistedSensorPrivacyStateLocked()) {
persistSensorPrivacyStateLocked();
}
}
mSensorPrivacyStateController = SensorPrivacyStateController.getInstance();
int[] micAndCameraOps = new int[]{OP_RECORD_AUDIO, OP_PHONE_CALL_MICROPHONE,
OP_CAMERA, OP_PHONE_CALL_CAMERA};
@@ -349,7 +276,26 @@ public final class SensorPrivacyService extends SystemService {
}
}, new IntentFilter(ACTION_DISABLE_INDIVIDUAL_SENSOR_PRIVACY),
MANAGE_SENSOR_PRIVACY, null, Context.RECEIVER_EXPORTED);
mContext.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
mSensorPrivacyStateController.forEachState(
(toggleType, userId, sensor, state) ->
logSensorPrivacyToggle(OTHER, sensor, state.isEnabled(),
state.getLastChange(), true)
);
}
}, new IntentFilter(Intent.ACTION_SHUTDOWN));
mUserManagerInternal.addUserRestrictionsListener(this);
mSensorPrivacyStateController.setAllSensorPrivacyListener(
mHandler, mHandler::handleSensorPrivacyChanged);
mSensorPrivacyStateController.setSensorPrivacyListener(
mHandler,
(toggleType, userId, sensor, state) -> mHandler.handleSensorPrivacyChanged(
userId, sensor, state.isEnabled()));
}
@Override
@@ -490,8 +436,9 @@ public final class SensorPrivacyService extends SystemService {
}
}
String inputMethodComponent = Settings.Secure.getString(mContext.getContentResolver(),
Settings.Secure.DEFAULT_INPUT_METHOD);
String inputMethodComponent = Settings.Secure.getStringForUser(
mContext.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD,
mCurrentUser);
String inputMethodPackageName = null;
if (inputMethodComponent != null) {
inputMethodPackageName = ComponentName.unflattenFromString(
@@ -546,7 +493,8 @@ public final class SensorPrivacyService extends SystemService {
private void enqueueSensorUseReminderDialogAsync(int taskId, @NonNull UserHandle user,
@NonNull String packageName, int sensor) {
mHandler.sendMessage(PooledLambda.obtainMessage(
this:: enqueueSensorUseReminderDialog, taskId, user, packageName, sensor));
SensorPrivacyServiceImpl::enqueueSensorUseReminderDialog, this, taskId, user,
packageName, sensor));
}
private void enqueueSensorUseReminderDialog(int taskId, @NonNull UserHandle user,
@@ -564,8 +512,8 @@ public final class SensorPrivacyService extends SystemService {
sensors.add(sensor);
}
mQueuedSensorUseReminderDialogs.put(info, sensors);
mHandler.sendMessageDelayed(
PooledLambda.obtainMessage(this::showSensorUserReminderDialog, info),
mHandler.sendMessageDelayed(PooledLambda.obtainMessage(
SensorPrivacyServiceImpl::showSensorUserReminderDialog, this, info),
REMINDER_DIALOG_DELAY_MILLIS);
return;
}
@@ -655,28 +603,32 @@ public final class SensorPrivacyService extends SystemService {
notificationManager.createNotificationChannel(channel);
Icon icon = Icon.createWithResource(getUiContext().getResources(), iconRes);
String contentTitle = getUiContext().getString(messageRes);
Spanned contentText = Html.fromHtml(getUiContext().getString(
R.string.sensor_privacy_start_use_notification_content_text, packageLabel), 0);
PendingIntent contentIntent = PendingIntent.getActivity(mContext, sensor,
new Intent(Settings.ACTION_PRIVACY_SETTINGS),
PendingIntent.FLAG_IMMUTABLE
| PendingIntent.FLAG_UPDATE_CURRENT);
String actionTitle = getUiContext().getString(
R.string.sensor_privacy_start_use_dialog_turn_on_button);
PendingIntent actionIntent = PendingIntent.getBroadcast(mContext, sensor,
new Intent(ACTION_DISABLE_INDIVIDUAL_SENSOR_PRIVACY)
.setPackage(mContext.getPackageName())
.putExtra(EXTRA_SENSOR, sensor)
.putExtra(Intent.EXTRA_USER, user),
PendingIntent.FLAG_IMMUTABLE
| PendingIntent.FLAG_UPDATE_CURRENT);
notificationManager.notify(notificationId,
new Notification.Builder(mContext, SENSOR_PRIVACY_CHANNEL_ID)
.setContentTitle(getUiContext().getString(messageRes))
.setContentText(Html.fromHtml(getUiContext().getString(
R.string.sensor_privacy_start_use_notification_content_text,
packageLabel),0))
.setContentTitle(contentTitle)
.setContentText(contentText)
.setSmallIcon(icon)
.addAction(new Notification.Action.Builder(icon,
getUiContext().getString(
R.string.sensor_privacy_start_use_dialog_turn_on_button),
PendingIntent.getBroadcast(mContext, sensor,
new Intent(ACTION_DISABLE_INDIVIDUAL_SENSOR_PRIVACY)
.setPackage(mContext.getPackageName())
.putExtra(EXTRA_SENSOR, sensor)
.putExtra(Intent.EXTRA_USER, user),
PendingIntent.FLAG_IMMUTABLE
| PendingIntent.FLAG_UPDATE_CURRENT))
.build())
.setContentIntent(PendingIntent.getActivity(mContext, sensor,
new Intent(Settings.ACTION_PRIVACY_SETTINGS),
PendingIntent.FLAG_IMMUTABLE
| PendingIntent.FLAG_UPDATE_CURRENT))
actionTitle, actionIntent).build())
.setContentIntent(contentIntent)
.extend(new Notification.TvExtender())
.setTimeoutAfter(isTelevision(mContext)
? /* dismiss immediately */ 1
@@ -697,16 +649,7 @@ public final class SensorPrivacyService extends SystemService {
@Override
public void setSensorPrivacy(boolean enable) {
enforceManageSensorPrivacyPermission();
// 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) {
synchronized (mLock) {
mEnabled.put(userId, enable);
persistSensorPrivacyStateLocked();
}
mHandler.onSensorPrivacyChanged(enable);
mSensorPrivacyStateController.setAllSensorState(enable);
}
@Override
@@ -735,43 +678,23 @@ public final class SensorPrivacyService extends SystemService {
private void setIndividualSensorPrivacyUnchecked(int userId, int source, int sensor,
boolean enable) {
synchronized (mLock) {
SparseArray<SensorState> userIndividualEnabled = mIndividualEnabled.get(userId,
new SparseArray<>());
SensorState sensorState = userIndividualEnabled.get(sensor);
long lastChange;
if (sensorState != null) {
lastChange = sensorState.mLastChange;
if (!sensorState.setEnabled(enable)) {
// State not changing
return;
}
} else {
sensorState = new SensorState(enable);
lastChange = sensorState.mLastChange;
userIndividualEnabled.put(sensor, sensorState);
}
mIndividualEnabled.put(userId, userIndividualEnabled);
if (userId == mUserManagerInternal.getProfileParentId(userId)) {
logSensorPrivacyToggle(source, sensor, sensorState.mEnabled, lastChange);
}
if (!enable) {
final long token = Binder.clearCallingIdentity();
try {
// Remove any notifications prompting the user to disable sensory privacy
NotificationManager notificationManager =
mContext.getSystemService(NotificationManager.class);
notificationManager.cancel(sensor);
} finally {
Binder.restoreCallingIdentity(token);
}
}
persistSensorPrivacyState();
}
mHandler.onSensorPrivacyChanged(userId, sensor, enable);
final long[] lastChange = new long[1];
mSensorPrivacyStateController.atomic(() -> {
SensorState sensorState = mSensorPrivacyStateController
.getState(SensorPrivacyManager.ToggleTypes.SOFTWARE, userId, sensor);
lastChange[0] = sensorState.getLastChange();
mSensorPrivacyStateController.setState(
SensorPrivacyManager.ToggleTypes.SOFTWARE, userId, sensor, enable, mHandler,
changeSuccessful -> {
if (changeSuccessful) {
if (userId == mUserManagerInternal.getProfileParentId(userId)) {
mHandler.sendMessage(PooledLambda.obtainMessage(
SensorPrivacyServiceImpl::logSensorPrivacyToggle, this,
source, sensor, enable, lastChange[0], false));
}
}
});
});
}
private boolean canChangeIndividualSensorPrivacy(@UserIdInt int userId, int sensor) {
@@ -801,14 +724,23 @@ public final class SensorPrivacyService extends SystemService {
}
private void logSensorPrivacyToggle(int source, int sensor, boolean enabled,
long lastChange) {
long lastChange, boolean onShutDown) {
long logMins = Math.max(0, (getCurrentTimeMillis() - lastChange) / (1000 * 60));
int logAction = -1;
if (enabled) {
logAction = PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_OFF;
if (onShutDown) {
// TODO ACTION_POWER_OFF_WHILE_(ON/OFF)
if (enabled) {
logAction = PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__ACTION_UNKNOWN;
} else {
logAction = PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__ACTION_UNKNOWN;
}
} else {
logAction = PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_ON;
if (enabled) {
logAction = PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_OFF;
} else {
logAction = PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_ON;
}
}
int logSensor = -1;
@@ -895,13 +827,7 @@ public final class SensorPrivacyService extends SystemService {
@Override
public boolean isSensorPrivacyEnabled() {
enforceObserveSensorPrivacyPermission();
return isSensorPrivacyEnabled(USER_SYSTEM);
}
private boolean isSensorPrivacyEnabled(@UserIdInt int userId) {
synchronized (mLock) {
return mEnabled.get(userId, false);
}
return mSensorPrivacyStateController.getAllSensorState();
}
@Override
@@ -918,239 +844,8 @@ public final class SensorPrivacyService extends SystemService {
if (userId == UserHandle.USER_CURRENT) {
userId = mCurrentUser;
}
synchronized (mLock) {
return isIndividualSensorPrivacyEnabledLocked(userId, sensor);
}
}
private boolean isIndividualSensorPrivacyEnabledLocked(int userId, int sensor) {
SparseArray<SensorState> states = mIndividualEnabled.get(userId);
if (states == null) {
return false;
}
SensorState state = states.get(sensor);
if (state == null) {
return false;
}
return state.mEnabled;
}
/**
* Returns the state of sensor privacy from persistent storage.
*/
private boolean readPersistedSensorPrivacyStateLocked() {
// if the file does not exist then sensor privacy has not yet been enabled on
// the device.
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);
SparseArray<SensorState> individualEnabled = new SparseArray<>();
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, new SensorState(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<SparseArray<SensorState>> 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.getAttributeInt(null, XML_ATTRIBUTE_SENSOR);
boolean isEnabled = parser.getAttributeBoolean(null,
XML_ATTRIBUTE_ENABLED);
long lastChange = parser
.getAttributeLong(null, XML_ATTRIBUTE_LAST_CHANGE, -1);
SparseArray<SensorState> userIndividualEnabled =
individualEnabled.get(currentUserId, new SparseArray<>());
userIndividualEnabled
.put(sensor, new SensorState(isEnabled, lastChange));
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;
}
}
try {
return upgradeAndInit(version, map);
} catch (Exception e) {
Log.wtf(TAG, "Failed to upgrade and set sensor privacy state,"
+ " resetting to default.", e);
mEnabled = new SparseBooleanArray();
mIndividualEnabled = new SparseArray<>();
return true;
}
}
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 SparseArray<>()));
return true;
}
boolean upgraded = false;
final int[] users = getLocalService(UserManagerInternal.class).getUserIds();
if (version == 0) {
final boolean enabled = (boolean) map.get(VER0_ENABLED);
final SparseArray<SensorState> individualEnabled =
(SparseArray<SensorState>) map.get(VER0_INDIVIDUAL_ENABLED);
final SparseBooleanArray perUserEnabled = new SparseBooleanArray();
final SparseArray<SparseArray<SensorState>> 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);
SparseArray<SensorState> userIndividualSensorEnabled = new SparseArray<>();
perUserIndividualEnabled.put(user, userIndividualSensorEnabled);
for (int j = 0; j < individualEnabled.size(); j++) {
final int sensor = individualEnabled.keyAt(j);
final SensorState isSensorEnabled = individualEnabled.valueAt(j);
userIndividualSensorEnabled.put(sensor, isSensorEnabled);
}
}
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<SparseArray<SensorState>>) map.get(VER1_INDIVIDUAL_ENABLED);
}
return upgraded;
}
/**
* Persists the state of sensor privacy.
*/
private void persistSensorPrivacyState() {
synchronized (mLock) {
persistSensorPrivacyStateLocked();
}
}
private void persistSensorPrivacyStateLocked() {
FileOutputStream outputStream = null;
try {
outputStream = mAtomicFile.startWrite();
TypedXmlSerializer serializer = Xml.resolveSerializer(outputStream);
serializer.startDocument(null, true);
serializer.startTag(null, XML_TAG_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));
SparseArray<SensorState> individualEnabled =
mIndividualEnabled.get(userId, new SparseArray<>());
int numIndividual = individualEnabled.size();
for (int i = 0; i < numIndividual; i++) {
serializer.startTag(null, XML_TAG_INDIVIDUAL_SENSOR_PRIVACY);
int sensor = individualEnabled.keyAt(i);
SensorState sensorState = individualEnabled.valueAt(i);
boolean enabled = sensorState.mEnabled;
long lastChange = sensorState.mLastChange;
serializer.attributeInt(null, XML_ATTRIBUTE_SENSOR, sensor);
serializer.attributeBoolean(null, XML_ATTRIBUTE_ENABLED, enabled);
serializer.attributeLong(null, XML_ATTRIBUTE_LAST_CHANGE, lastChange);
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);
} catch (IOException e) {
Log.e(TAG, "Caught an exception persisting the sensor privacy state: ", e);
mAtomicFile.failWrite(outputStream);
}
return mSensorPrivacyStateController.getState(SensorPrivacyManager.ToggleTypes.SOFTWARE,
userId, sensor).isEnabled();
}
@Override
@@ -1285,23 +980,23 @@ public final class SensorPrivacyService extends SystemService {
}
private void userSwitching(int from, int to) {
boolean micState;
boolean camState;
boolean prevMicState;
boolean prevCamState;
synchronized (mLock) {
prevMicState = isIndividualSensorPrivacyEnabledLocked(from, MICROPHONE);
prevCamState = isIndividualSensorPrivacyEnabledLocked(from, CAMERA);
micState = isIndividualSensorPrivacyEnabledLocked(to, MICROPHONE);
camState = isIndividualSensorPrivacyEnabledLocked(to, CAMERA);
final boolean[] micState = new boolean[1];
final boolean[] camState = new boolean[1];
final boolean[] prevMicState = new boolean[1];
final boolean[] prevCamState = new boolean[1];
mSensorPrivacyStateController.atomic(() -> {
prevMicState[0] = isIndividualSensorPrivacyEnabled(from, MICROPHONE);
prevCamState[0] = isIndividualSensorPrivacyEnabled(from, CAMERA);
micState[0] = isIndividualSensorPrivacyEnabled(to, MICROPHONE);
camState[0] = isIndividualSensorPrivacyEnabled(to, CAMERA);
});
if (from == USER_NULL || prevMicState[0] != micState[0]) {
mHandler.onUserGlobalSensorPrivacyChanged(MICROPHONE, micState[0]);
setGlobalRestriction(MICROPHONE, micState[0]);
}
if (from == USER_NULL || prevMicState != micState) {
mHandler.onUserGlobalSensorPrivacyChanged(MICROPHONE, micState);
setGlobalRestriction(MICROPHONE, micState);
}
if (from == USER_NULL || prevCamState != camState) {
mHandler.onUserGlobalSensorPrivacyChanged(CAMERA, camState);
setGlobalRestriction(CAMERA, camState);
if (from == USER_NULL || prevCamState[0] != camState[0]) {
mHandler.onUserGlobalSensorPrivacyChanged(CAMERA, camState[0]);
setGlobalRestriction(CAMERA, camState[0]);
}
}
@@ -1395,57 +1090,20 @@ public final class SensorPrivacyService extends SystemService {
final long identity = Binder.clearCallingIdentity();
try {
if (dumpAsProto) {
dump(new DualDumpOutputStream(new ProtoOutputStream(fd)));
mSensorPrivacyStateController.dump(
new DualDumpOutputStream(new ProtoOutputStream(fd)));
} else {
pw.println("SENSOR PRIVACY MANAGER STATE (dumpsys "
+ Context.SENSOR_PRIVACY_SERVICE + ")");
dump(new DualDumpOutputStream(new IndentingPrintWriter(pw, " ")));
mSensorPrivacyStateController.dump(
new DualDumpOutputStream(new IndentingPrintWriter(pw, " ")));
}
} finally {
Binder.restoreCallingIdentity(identity);
}
}
/**
* Dump state to {@link DualDumpOutputStream}.
*
* @param dumpStream The destination to dump to
*/
private void dump(@NonNull DualDumpOutputStream dumpStream) {
synchronized (mLock) {
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));
SparseArray<SensorState> 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.write("sensor",
SensorPrivacyIndividualEnabledSensorProto.SENSOR,
individualEnabled.keyAt(i));
dumpStream.write("is_enabled",
SensorPrivacyIndividualEnabledSensorProto.IS_ENABLED,
individualEnabled.valueAt(i).mEnabled);
// TODO dump last change
dumpStream.end(individualToken);
}
}
dumpStream.end(userToken);
});
}
dumpStream.flush();
}
/**
* Convert a string into a {@link SensorPrivacyManager.Sensors.Sensor id}.
*
@@ -1504,25 +1162,6 @@ public final class SensorPrivacyService extends SystemService {
setIndividualSensorPrivacy(userId, SHELL, sensor, false);
}
break;
case "reset": {
int sensor = sensorStrToId(getNextArgRequired());
if (sensor == UNKNOWN) {
pw.println("Invalid sensor");
return -1;
}
enforceManageSensorPrivacyPermission();
synchronized (mLock) {
SparseArray<SensorState> individualEnabled =
mIndividualEnabled.get(userId);
if (individualEnabled != null) {
individualEnabled.delete(sensor);
}
persistSensorPrivacyState();
}
}
break;
default:
return handleDefaultCommands(cmd);
}
@@ -1545,9 +1184,6 @@ public final class SensorPrivacyService extends SystemService {
pw.println(" disable USER_ID SENSOR");
pw.println(" Disable privacy for a certain sensor.");
pw.println("");
pw.println(" reset USER_ID SENSOR");
pw.println(" Reset privacy state for a certain sensor.");
pw.println("");
}
}).exec(this, in, out, err, args, callback, resultReceiver);
}
@@ -1581,22 +1217,6 @@ public final class SensorPrivacyService extends SystemService {
mContext = context;
}
public void onSensorPrivacyChanged(boolean enabled) {
sendMessage(PooledLambda.obtainMessage(SensorPrivacyHandler::handleSensorPrivacyChanged,
this, enabled));
sendMessage(
PooledLambda.obtainMessage(SensorPrivacyServiceImpl::persistSensorPrivacyState,
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 onUserGlobalSensorPrivacyChanged(int sensor, boolean enabled) {
sendMessage(PooledLambda.obtainMessage(
SensorPrivacyHandler::handleUserGlobalSensorPrivacyChanged,
@@ -1692,6 +1312,7 @@ public final class SensorPrivacyService extends SystemService {
}
public void handleSensorPrivacyChanged(int userId, int sensor, boolean enabled) {
// TODO handle hardware
mSensorPrivacyManagerInternal.dispatch(userId, sensor, enabled);
SparseArray<RemoteCallbackList<ISensorPrivacyListener>> listenersForUser =
mIndividualSensorListeners.get(userId);
@@ -1972,11 +1593,7 @@ public final class SensorPrivacyService extends SystemService {
}
}
private static long getCurrentTimeMillis() {
try {
return SystemClock.currentNetworkTimeMillis();
} catch (Exception e) {
return System.currentTimeMillis();
}
static long getCurrentTimeMillis() {
return SystemClock.elapsedRealtime();
}
}

View File

@@ -0,0 +1,158 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.sensorprivacy;
import android.os.Handler;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.dump.DualDumpOutputStream;
import com.android.internal.util.function.pooled.PooledLambda;
abstract class SensorPrivacyStateController {
private static SensorPrivacyStateController sInstance;
AllSensorStateController mAllSensorStateController = AllSensorStateController.getInstance();
private final Object mLock = new Object();
static SensorPrivacyStateController getInstance() {
if (sInstance == null) {
sInstance = SensorPrivacyStateControllerImpl.getInstance();
}
return sInstance;
}
SensorState getState(int toggleType, int userId, int sensor) {
synchronized (mLock) {
return getStateLocked(toggleType, userId, sensor);
}
}
void setState(int toggleType, int userId, int sensor, boolean enabled, Handler callbackHandler,
SetStateResultCallback callback) {
synchronized (mLock) {
setStateLocked(toggleType, userId, sensor, enabled, callbackHandler, callback);
}
}
void setSensorPrivacyListener(Handler handler,
SensorPrivacyListener listener) {
synchronized (mLock) {
setSensorPrivacyListenerLocked(handler, listener);
}
}
// Following calls are for the developer settings sensor mute feature
boolean getAllSensorState() {
synchronized (mLock) {
return mAllSensorStateController.getAllSensorStateLocked();
}
}
void setAllSensorState(boolean enable) {
synchronized (mLock) {
mAllSensorStateController.setAllSensorStateLocked(enable);
}
}
void setAllSensorPrivacyListener(Handler handler, AllSensorPrivacyListener listener) {
synchronized (mLock) {
mAllSensorStateController.setAllSensorPrivacyListenerLocked(handler, listener);
}
}
void persistAll() {
synchronized (mLock) {
mAllSensorStateController.schedulePersistLocked();
schedulePersistLocked();
}
}
void forEachState(SensorPrivacyStateConsumer consumer) {
synchronized (mLock) {
forEachStateLocked(consumer);
}
}
void dump(DualDumpOutputStream dumpStream) {
synchronized (mLock) {
mAllSensorStateController.dumpLocked(dumpStream);
dumpLocked(dumpStream);
}
dumpStream.flush();
}
public void atomic(Runnable r) {
synchronized (mLock) {
r.run();
}
}
interface SensorPrivacyListener {
void onSensorPrivacyChanged(int toggleType, int userId, int sensor, SensorState state);
}
interface SensorPrivacyStateConsumer {
void accept(int toggleType, int userId, int sensor, SensorState state);
}
interface SetStateResultCallback {
void callback(boolean changed);
}
interface AllSensorPrivacyListener {
void onAllSensorPrivacyChanged(boolean enabled);
}
@GuardedBy("mLock")
abstract SensorState getStateLocked(int toggleType, int userId, int sensor);
@GuardedBy("mLock")
abstract void setStateLocked(int toggleType, int userId, int sensor, boolean enabled,
Handler callbackHandler, SetStateResultCallback callback);
@GuardedBy("mLock")
abstract void setSensorPrivacyListenerLocked(Handler handler,
SensorPrivacyListener listener);
@GuardedBy("mLock")
abstract void schedulePersistLocked();
@GuardedBy("mLock")
abstract void forEachStateLocked(SensorPrivacyStateConsumer consumer);
@GuardedBy("mLock")
abstract void dumpLocked(DualDumpOutputStream dumpStream);
static void sendSetStateCallback(Handler callbackHandler,
SetStateResultCallback callback, boolean success) {
callbackHandler.sendMessage(PooledLambda.obtainMessage(SetStateResultCallback::callback,
callback, success));
}
/**
* Used for unit testing
*/
void resetForTesting() {
mAllSensorStateController.resetForTesting();
resetForTestingImpl();
sInstance = null;
}
abstract void resetForTestingImpl();
}

View File

@@ -0,0 +1,142 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.sensorprivacy;
import android.hardware.SensorPrivacyManager;
import android.os.Handler;
import com.android.internal.util.dump.DualDumpOutputStream;
import com.android.internal.util.function.pooled.PooledLambda;
import java.util.Objects;
class SensorPrivacyStateControllerImpl extends SensorPrivacyStateController {
private static final String SENSOR_PRIVACY_XML_FILE = "sensor_privacy_impl.xml";
private static SensorPrivacyStateControllerImpl sInstance;
private PersistedState mPersistedState;
private SensorPrivacyListener mListener;
private Handler mListenerHandler;
static SensorPrivacyStateController getInstance() {
if (sInstance == null) {
sInstance = new SensorPrivacyStateControllerImpl();
}
return sInstance;
}
private SensorPrivacyStateControllerImpl() {
mPersistedState = PersistedState.fromFile(SENSOR_PRIVACY_XML_FILE);
persistAll();
}
@Override
SensorState getStateLocked(int toggleType, int userId, int sensor) {
if (toggleType == SensorPrivacyManager.ToggleTypes.HARDWARE) {
// Device doesn't support hardware state
return getDefaultSensorState();
}
SensorState sensorState = mPersistedState.getState(toggleType, userId, sensor);
if (sensorState != null) {
return new SensorState(sensorState);
}
return getDefaultSensorState();
}
private static SensorState getDefaultSensorState() {
return new SensorState(false);
}
@Override
void setStateLocked(int toggleType, int userId, int sensor, boolean enabled,
Handler callbackHandler, SetStateResultCallback callback) {
if (toggleType != SensorPrivacyManager.ToggleTypes.SOFTWARE) {
// Implementation only supports software switch
callbackHandler.sendMessage(PooledLambda.obtainMessage(
SetStateResultCallback::callback, callback, false));
return;
}
// Changing the SensorState's mEnabled updates the timestamp of its last change.
// A nonexistent state -> unmuted should not set the timestamp.
SensorState lastState = mPersistedState.getState(toggleType, userId, sensor);
if (lastState == null) {
if (!enabled) {
sendSetStateCallback(callbackHandler, callback, false);
return;
} else if (enabled) {
SensorState sensorState = new SensorState(true);
mPersistedState.setState(toggleType, userId, sensor, sensorState);
notifyStateChangeLocked(toggleType, userId, sensor, sensorState);
sendSetStateCallback(callbackHandler, callback, true);
return;
}
}
if (lastState.setEnabled(enabled)) {
notifyStateChangeLocked(toggleType, userId, sensor, lastState);
sendSetStateCallback(callbackHandler, callback, true);
return;
}
sendSetStateCallback(callbackHandler, callback, false);
}
private void notifyStateChangeLocked(int toggleType, int userId, int sensor,
SensorState sensorState) {
if (mListenerHandler != null && mListener != null) {
mListenerHandler.sendMessage(PooledLambda.obtainMessage(
SensorPrivacyListener::onSensorPrivacyChanged, mListener,
toggleType, userId, sensor, new SensorState(sensorState)));
}
schedulePersistLocked();
}
@Override
void setSensorPrivacyListenerLocked(Handler handler, SensorPrivacyListener listener) {
Objects.requireNonNull(handler);
Objects.requireNonNull(listener);
if (mListener != null) {
throw new IllegalStateException("Listener is already set");
}
mListener = listener;
mListenerHandler = handler;
}
@Override
void schedulePersistLocked() {
mPersistedState.schedulePersist();
}
@Override
void forEachStateLocked(SensorPrivacyStateConsumer consumer) {
mPersistedState.forEachKnownState(consumer::accept);
}
@Override
void resetForTestingImpl() {
mPersistedState.resetForTesting();
mListener = null;
mListenerHandler = null;
sInstance = null;
}
@Override
void dumpLocked(DualDumpOutputStream dumpStream) {
mPersistedState.dump(dumpStream);
}
}

View File

@@ -0,0 +1,79 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.sensorprivacy;
import static android.hardware.SensorPrivacyManager.StateTypes.DISABLED;
import static android.hardware.SensorPrivacyManager.StateTypes.ENABLED;
import static com.android.server.sensorprivacy.SensorPrivacyService.getCurrentTimeMillis;
class SensorState {
private int mStateType;
private long mLastChange;
SensorState(int stateType) {
mStateType = stateType;
mLastChange = getCurrentTimeMillis();
}
SensorState(int stateType, long lastChange) {
mStateType = stateType;
mLastChange = Math.min(getCurrentTimeMillis(), lastChange);
}
SensorState(SensorState sensorState) {
mStateType = sensorState.getState();
mLastChange = sensorState.getLastChange();
}
boolean setState(int stateType) {
if (mStateType != stateType) {
mStateType = stateType;
mLastChange = getCurrentTimeMillis();
return true;
}
return false;
}
int getState() {
return mStateType;
}
long getLastChange() {
return mLastChange;
}
// Below are some convenience members for when dealing with enabled/disabled
private static int enabledToState(boolean enabled) {
return enabled ? ENABLED : DISABLED;
}
SensorState(boolean enabled) {
this(enabledToState(enabled));
}
boolean setEnabled(boolean enabled) {
return setState(enabledToState(enabled));
}
boolean isEnabled() {
return getState() == ENABLED;
}
}

View File

@@ -181,6 +181,7 @@ import com.android.server.security.AttestationVerificationManagerService;
import com.android.server.security.FileIntegrityService;
import com.android.server.security.KeyAttestationApplicationIdProviderService;
import com.android.server.security.KeyChainSystemService;
import com.android.server.sensorprivacy.SensorPrivacyService;
import com.android.server.sensors.SensorService;
import com.android.server.signedconfig.SignedConfigService;
import com.android.server.soundtrigger.SoundTriggerService;

View File

@@ -1,7 +1,7 @@
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<sensor-privacy persistence-version="1" version="1">
<user id="0" enabled="false">
<individual-sensor-privacy sensor="1" enabled="true" />
<individual-sensor-privacy sensor="2" enabled="true" />
<individual-sensor-privacy sensor="1" enabled="true" last-change="100" />
<individual-sensor-privacy sensor="2" enabled="true" last-change="100" />
</user>
</sensor-privacy>

View File

@@ -0,0 +1,5 @@
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<sensor-privacy persistence-version="2" version="2">
<sensor-state toggle-type="1" user-id="0" sensor="1" state-type="1" last-change="123" />
<sensor-state toggle-type="1" user-id="0" sensor="2" state-type="2" last-change="123" />
</sensor-privacy>

View File

@@ -0,0 +1,5 @@
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<sensor-privacy persistence-version="2" version="2">
<sensor-state toggle-type="2" user-id="0" sensor="1" state-type="1" last-change="1234" />
<sensor-state toggle-type="2" user-id="0" sensor="2" state-type="2" last-change="1234" />
</sensor-privacy>

View File

@@ -16,43 +16,47 @@
package com.android.server.sensorprivacy;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE;
import static android.hardware.SensorPrivacyManager.ToggleTypes.HARDWARE;
import static android.hardware.SensorPrivacyManager.ToggleTypes.SOFTWARE;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.AppOpsManager;
import android.app.AppOpsManagerInternal;
import android.content.Context;
import android.content.pm.UserInfo;
import android.hardware.SensorPrivacyManager;
import android.os.Environment;
import android.telephony.TelephonyManager;
import android.os.Handler;
import android.testing.AndroidTestingRunner;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.server.LocalServices;
import com.android.server.SensorPrivacyService;
import com.android.server.SystemService;
import com.android.server.pm.UserManagerInternal;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.ArgumentCaptor;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.concurrent.CompletableFuture;
import java.nio.file.StandardCopyOption;
@RunWith(AndroidTestingRunner.class)
public class SensorPrivacyServiceMockingTest {
@@ -71,6 +75,10 @@ public class SensorPrivacyServiceMockingTest {
String.format(PERSISTENCE_FILE_PATHS_TEMPLATE, 5);
public static final String PERSISTENCE_FILE6 =
String.format(PERSISTENCE_FILE_PATHS_TEMPLATE, 6);
public static final String PERSISTENCE_FILE7 =
String.format(PERSISTENCE_FILE_PATHS_TEMPLATE, 7);
public static final String PERSISTENCE_FILE8 =
String.format(PERSISTENCE_FILE_PATHS_TEMPLATE, 8);
public static final String PERSISTENCE_FILE_MIC_MUTE_CAM_MUTE =
"SensorPrivacyServiceMockingTest/persisted_file_micMute_camMute.xml";
@@ -81,176 +89,281 @@ public class SensorPrivacyServiceMockingTest {
public static final String PERSISTENCE_FILE_MIC_UNMUTE_CAM_UNMUTE =
"SensorPrivacyServiceMockingTest/persisted_file_micUnmute_camUnmute.xml";
private Context mContext;
@Mock
private AppOpsManager mMockedAppOpsManager;
@Mock
private AppOpsManagerInternal mMockedAppOpsManagerInternal;
@Mock
private UserManagerInternal mMockedUserManagerInternal;
@Mock
private ActivityManager mMockedActivityManager;
@Mock
private ActivityTaskManager mMockedActivityTaskManager;
@Mock
private TelephonyManager mMockedTelephonyManager;
Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
String mDataDir = mContext.getApplicationInfo().dataDir;
@Before
public void setUp() {
new File(mDataDir, "sensor_privacy.xml").delete();
new File(mDataDir, "sensor_privacy_impl.xml").delete();
}
@Test
public void testServiceInit() throws IOException {
public void testMigration1() throws IOException {
PersistedState ps = migrateFromFile(PERSISTENCE_FILE1);
assertTrue(ps.getState(SOFTWARE, 0, MICROPHONE).isEnabled());
assertTrue(ps.getState(SOFTWARE, 0, CAMERA).isEnabled());
assertNull(ps.getState(SOFTWARE, 10, MICROPHONE));
assertNull(ps.getState(SOFTWARE, 10, CAMERA));
assertNull(ps.getState(HARDWARE, 0, MICROPHONE));
assertNull(ps.getState(HARDWARE, 0, CAMERA));
assertNull(ps.getState(HARDWARE, 10, MICROPHONE));
assertNull(ps.getState(HARDWARE, 10, CAMERA));
}
@Test
public void testMigration2() throws IOException {
PersistedState ps = migrateFromFile(PERSISTENCE_FILE2);
assertTrue(ps.getState(SOFTWARE, 0, MICROPHONE).isEnabled());
assertTrue(ps.getState(SOFTWARE, 0, CAMERA).isEnabled());
assertTrue(ps.getState(SOFTWARE, 10, MICROPHONE).isEnabled());
assertFalse(ps.getState(SOFTWARE, 10, CAMERA).isEnabled());
assertNull(ps.getState(SOFTWARE, 11, MICROPHONE));
assertNull(ps.getState(SOFTWARE, 11, CAMERA));
assertTrue(ps.getState(SOFTWARE, 12, MICROPHONE).isEnabled());
assertNull(ps.getState(SOFTWARE, 12, CAMERA));
assertNull(ps.getState(HARDWARE, 0, MICROPHONE));
assertNull(ps.getState(HARDWARE, 0, CAMERA));
assertNull(ps.getState(HARDWARE, 10, MICROPHONE));
assertNull(ps.getState(HARDWARE, 10, CAMERA));
assertNull(ps.getState(HARDWARE, 11, MICROPHONE));
assertNull(ps.getState(HARDWARE, 11, CAMERA));
assertNull(ps.getState(HARDWARE, 12, MICROPHONE));
assertNull(ps.getState(HARDWARE, 12, CAMERA));
}
@Test
public void testMigration3() throws IOException {
PersistedState ps = migrateFromFile(PERSISTENCE_FILE3);
assertFalse(ps.getState(SOFTWARE, 0, MICROPHONE).isEnabled());
assertFalse(ps.getState(SOFTWARE, 0, CAMERA).isEnabled());
assertNull(ps.getState(SOFTWARE, 10, MICROPHONE));
assertNull(ps.getState(SOFTWARE, 10, CAMERA));
assertNull(ps.getState(HARDWARE, 0, MICROPHONE));
assertNull(ps.getState(HARDWARE, 0, CAMERA));
assertNull(ps.getState(HARDWARE, 10, MICROPHONE));
assertNull(ps.getState(HARDWARE, 10, CAMERA));
}
@Test
public void testMigration4() throws IOException {
PersistedState ps = migrateFromFile(PERSISTENCE_FILE4);
assertTrue(ps.getState(SOFTWARE, 0, MICROPHONE).isEnabled());
assertFalse(ps.getState(SOFTWARE, 0, CAMERA).isEnabled());
assertFalse(ps.getState(SOFTWARE, 10, MICROPHONE).isEnabled());
assertNull(ps.getState(SOFTWARE, 10, CAMERA));
assertNull(ps.getState(HARDWARE, 0, MICROPHONE));
assertNull(ps.getState(HARDWARE, 0, CAMERA));
assertNull(ps.getState(HARDWARE, 10, MICROPHONE));
assertNull(ps.getState(HARDWARE, 10, CAMERA));
}
@Test
public void testMigration5() throws IOException {
PersistedState ps = migrateFromFile(PERSISTENCE_FILE5);
assertNull(ps.getState(SOFTWARE, 0, MICROPHONE));
assertFalse(ps.getState(SOFTWARE, 0, CAMERA).isEnabled());
assertNull(ps.getState(SOFTWARE, 10, MICROPHONE));
assertFalse(ps.getState(SOFTWARE, 10, CAMERA).isEnabled());
assertNull(ps.getState(HARDWARE, 0, MICROPHONE));
assertNull(ps.getState(HARDWARE, 0, CAMERA));
assertNull(ps.getState(HARDWARE, 10, MICROPHONE));
assertNull(ps.getState(HARDWARE, 10, CAMERA));
}
@Test
public void testMigration6() throws IOException {
PersistedState ps = migrateFromFile(PERSISTENCE_FILE6);
assertNull(ps.getState(SOFTWARE, 0, MICROPHONE));
assertNull(ps.getState(SOFTWARE, 0, CAMERA));
assertNull(ps.getState(SOFTWARE, 10, MICROPHONE));
assertNull(ps.getState(SOFTWARE, 10, CAMERA));
assertNull(ps.getState(HARDWARE, 0, MICROPHONE));
assertNull(ps.getState(HARDWARE, 0, CAMERA));
assertNull(ps.getState(HARDWARE, 10, MICROPHONE));
assertNull(ps.getState(HARDWARE, 10, CAMERA));
}
private PersistedState migrateFromFile(String fileName) throws IOException {
MockitoSession mockitoSession = ExtendedMockito.mockitoSession()
.initMocks(this)
.strictness(Strictness.WARN)
.spyStatic(LocalServices.class)
.spyStatic(Environment.class)
.startMocking();
try {
mContext = InstrumentationRegistry.getInstrumentation().getContext();
spyOn(mContext);
doReturn(new File(mDataDir)).when(() -> Environment.getDataSystemDirectory());
doReturn(mMockedAppOpsManager).when(mContext).getSystemService(AppOpsManager.class);
doReturn(mMockedUserManagerInternal)
.when(() -> LocalServices.getService(UserManagerInternal.class));
doReturn(mMockedActivityManager).when(mContext).getSystemService(ActivityManager.class);
doReturn(mMockedActivityTaskManager)
.when(mContext).getSystemService(ActivityTaskManager.class);
doReturn(mMockedTelephonyManager).when(mContext).getSystemService(
TelephonyManager.class);
UserManagerInternal umi = mock(UserManagerInternal.class);
doReturn(umi).when(() -> LocalServices.getService(UserManagerInternal.class));
doReturn(new int[] {0}).when(umi).getUserIds();
String dataDir = mContext.getApplicationInfo().dataDir;
doReturn(new File(dataDir)).when(() -> Environment.getDataSystemDirectory());
File onDeviceFile = new File(dataDir, "sensor_privacy.xml");
onDeviceFile.delete();
// Try all files with one known user
doReturn(new int[]{0}).when(mMockedUserManagerInternal).getUserIds();
doReturn(ExtendedMockito.mock(UserInfo.class)).when(mMockedUserManagerInternal)
.getUserInfo(0);
initServiceWithPersistenceFile(onDeviceFile, null);
initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE1);
initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE2);
initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE3);
initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE4);
initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE5);
initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE6);
// Try all files with two known users
doReturn(new int[]{0, 10}).when(mMockedUserManagerInternal).getUserIds();
doReturn(ExtendedMockito.mock(UserInfo.class)).when(mMockedUserManagerInternal)
.getUserInfo(0);
doReturn(ExtendedMockito.mock(UserInfo.class)).when(mMockedUserManagerInternal)
.getUserInfo(10);
initServiceWithPersistenceFile(onDeviceFile, null);
initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE1);
initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE2);
initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE3);
initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE4);
initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE5);
initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE6);
Files.copy(
mContext.getAssets().open(fileName),
new File(mDataDir, "sensor_privacy.xml").toPath(),
StandardCopyOption.REPLACE_EXISTING);
return PersistedState.fromFile("sensor_privacy_impl.xml");
} finally {
mockitoSession.finishMocking();
}
}
@Test
public void testServiceInit_AppOpsRestricted_micMute_camMute() throws IOException {
testServiceInit_AppOpsRestricted(PERSISTENCE_FILE_MIC_MUTE_CAM_MUTE, true, true);
public void testPersistence1Version2() throws IOException {
PersistedState ps = getPersistedStateV2(PERSISTENCE_FILE7);
assertEquals(1, ps.getState(SOFTWARE, 0, MICROPHONE).getState());
assertEquals(123L, ps.getState(SOFTWARE, 0, MICROPHONE).getLastChange());
assertEquals(2, ps.getState(SOFTWARE, 0, CAMERA).getState());
assertEquals(123L, ps.getState(SOFTWARE, 0, CAMERA).getLastChange());
assertNull(ps.getState(HARDWARE, 0, MICROPHONE));
assertNull(ps.getState(HARDWARE, 0, CAMERA));
assertNull(ps.getState(SOFTWARE, 10, MICROPHONE));
assertNull(ps.getState(SOFTWARE, 10, CAMERA));
}
@Test
public void testServiceInit_AppOpsRestricted_micMute_camUnmute() throws IOException {
testServiceInit_AppOpsRestricted(PERSISTENCE_FILE_MIC_MUTE_CAM_UNMUTE, true, false);
public void testPersistence2Version2() throws IOException {
PersistedState ps = getPersistedStateV2(PERSISTENCE_FILE8);
assertEquals(1, ps.getState(HARDWARE, 0, MICROPHONE).getState());
assertEquals(1234L, ps.getState(HARDWARE, 0, MICROPHONE).getLastChange());
assertEquals(2, ps.getState(HARDWARE, 0, CAMERA).getState());
assertEquals(1234L, ps.getState(HARDWARE, 0, CAMERA).getLastChange());
assertNull(ps.getState(SOFTWARE, 0, MICROPHONE));
assertNull(ps.getState(SOFTWARE, 0, CAMERA));
assertNull(ps.getState(SOFTWARE, 10, MICROPHONE));
assertNull(ps.getState(SOFTWARE, 10, CAMERA));
assertNull(ps.getState(HARDWARE, 10, MICROPHONE));
assertNull(ps.getState(HARDWARE, 10, CAMERA));
}
@Test
public void testServiceInit_AppOpsRestricted_micUnmute_camMute() throws IOException {
testServiceInit_AppOpsRestricted(PERSISTENCE_FILE_MIC_UNMUTE_CAM_MUTE, false, true);
}
@Test
public void testServiceInit_AppOpsRestricted_micUnmute_camUnmute() throws IOException {
testServiceInit_AppOpsRestricted(PERSISTENCE_FILE_MIC_UNMUTE_CAM_UNMUTE, false, false);
}
private void testServiceInit_AppOpsRestricted(String persistenceFileMicMuteCamMute,
boolean expectedMicState, boolean expectedCamState)
throws IOException {
private PersistedState getPersistedStateV2(String version2FilePath) throws IOException {
MockitoSession mockitoSession = ExtendedMockito.mockitoSession()
.initMocks(this)
.strictness(Strictness.WARN)
.spyStatic(LocalServices.class)
.spyStatic(Environment.class)
.startMocking();
try {
mContext = InstrumentationRegistry.getInstrumentation().getContext();
spyOn(mContext);
doReturn(mMockedAppOpsManager).when(mContext).getSystemService(AppOpsManager.class);
doReturn(mMockedAppOpsManagerInternal)
.when(() -> LocalServices.getService(AppOpsManagerInternal.class));
doReturn(mMockedUserManagerInternal)
.when(() -> LocalServices.getService(UserManagerInternal.class));
doReturn(mMockedActivityManager).when(mContext).getSystemService(ActivityManager.class);
doReturn(mMockedActivityTaskManager)
.when(mContext).getSystemService(ActivityTaskManager.class);
doReturn(mMockedTelephonyManager).when(mContext).getSystemService(
TelephonyManager.class);
String dataDir = mContext.getApplicationInfo().dataDir;
doReturn(new File(dataDir)).when(() -> Environment.getDataSystemDirectory());
File onDeviceFile = new File(dataDir, "sensor_privacy.xml");
onDeviceFile.delete();
doReturn(new int[]{0}).when(mMockedUserManagerInternal).getUserIds();
doReturn(ExtendedMockito.mock(UserInfo.class)).when(mMockedUserManagerInternal)
.getUserInfo(0);
CompletableFuture<Boolean> micState = new CompletableFuture<>();
CompletableFuture<Boolean> camState = new CompletableFuture<>();
doAnswer(invocation -> {
int code = invocation.getArgument(0);
boolean restricted = invocation.getArgument(1);
if (code == AppOpsManager.OP_RECORD_AUDIO) {
micState.complete(restricted);
} else if (code == AppOpsManager.OP_CAMERA) {
camState.complete(restricted);
}
return null;
}).when(mMockedAppOpsManagerInternal).setGlobalRestriction(anyInt(), anyBoolean(),
any());
initServiceWithPersistenceFile(onDeviceFile, persistenceFileMicMuteCamMute, 0);
Assert.assertTrue(micState.join() == expectedMicState);
Assert.assertTrue(camState.join() == expectedCamState);
doReturn(new File(mDataDir)).when(() -> Environment.getDataSystemDirectory());
Files.copy(
mContext.getAssets().open(version2FilePath),
new File(mDataDir, "sensor_privacy_impl.xml").toPath(),
StandardCopyOption.REPLACE_EXISTING);
return PersistedState.fromFile("sensor_privacy_impl.xml");
} finally {
mockitoSession.finishMocking();
}
}
private void initServiceWithPersistenceFile(File onDeviceFile,
String persistenceFilePath) throws IOException {
initServiceWithPersistenceFile(onDeviceFile, persistenceFilePath, -1);
@Test
public void testGetDefaultState() {
MockitoSession mockitoSession = ExtendedMockito.mockitoSession()
.initMocks(this)
.strictness(Strictness.WARN)
.spyStatic(PersistedState.class)
.startMocking();
try {
PersistedState persistedState = mock(PersistedState.class);
doReturn(persistedState).when(() -> PersistedState.fromFile(any()));
doReturn(null).when(persistedState).getState(anyInt(), anyInt(), anyInt());
SensorPrivacyStateController sensorPrivacyStateController =
getSensorPrivacyStateControllerImpl();
SensorState micState = sensorPrivacyStateController.getState(SOFTWARE, 0, MICROPHONE);
SensorState camState = sensorPrivacyStateController.getState(SOFTWARE, 0, CAMERA);
assertEquals(SensorPrivacyManager.StateTypes.DISABLED, micState.getState());
assertEquals(SensorPrivacyManager.StateTypes.DISABLED, camState.getState());
verify(persistedState, times(1)).getState(SOFTWARE, 0, MICROPHONE);
verify(persistedState, times(1)).getState(SOFTWARE, 0, CAMERA);
} finally {
mockitoSession.finishMocking();
}
}
private void initServiceWithPersistenceFile(File onDeviceFile,
String persistenceFilePath, int startingUserId) throws IOException {
if (persistenceFilePath != null) {
Files.copy(mContext.getAssets().open(persistenceFilePath),
onDeviceFile.toPath());
@Test
public void testGetSetState() {
MockitoSession mockitoSession = ExtendedMockito.mockitoSession()
.initMocks(this)
.strictness(Strictness.WARN)
.spyStatic(PersistedState.class)
.startMocking();
try {
PersistedState persistedState = mock(PersistedState.class);
SensorState sensorState = mock(SensorState.class);
doReturn(persistedState).when(() -> PersistedState.fromFile(any()));
doReturn(sensorState).when(persistedState).getState(SOFTWARE, 0, MICROPHONE);
doReturn(SensorPrivacyManager.StateTypes.ENABLED).when(sensorState).getState();
doReturn(0L).when(sensorState).getLastChange();
SensorPrivacyStateController sensorPrivacyStateController =
getSensorPrivacyStateControllerImpl();
SensorState micState = sensorPrivacyStateController.getState(SOFTWARE, 0, MICROPHONE);
assertEquals(SensorPrivacyManager.StateTypes.ENABLED, micState.getState());
assertEquals(0L, micState.getLastChange());
} finally {
mockitoSession.finishMocking();
}
SensorPrivacyService service = new SensorPrivacyService(mContext);
if (startingUserId != -1) {
SystemService.TargetUser mockedTargetUser =
ExtendedMockito.mock(SystemService.TargetUser.class);
doReturn(startingUserId).when(mockedTargetUser).getUserIdentifier();
service.onUserStarting(mockedTargetUser);
}
@Test
public void testSetState() {
MockitoSession mockitoSession = ExtendedMockito.mockitoSession()
.initMocks(this)
.strictness(Strictness.WARN)
.spyStatic(PersistedState.class)
.startMocking();
try {
PersistedState persistedState = mock(PersistedState.class);
doReturn(persistedState).when(() -> PersistedState.fromFile(any()));
SensorPrivacyStateController sensorPrivacyStateController =
getSensorPrivacyStateControllerImpl();
sensorPrivacyStateController.setState(SOFTWARE, 0, MICROPHONE, true,
mock(Handler.class), changed -> {});
ArgumentCaptor<SensorState> captor = ArgumentCaptor.forClass(SensorState.class);
verify(persistedState, times(1)).setState(eq(SOFTWARE), eq(0), eq(MICROPHONE),
captor.capture());
assertEquals(SensorPrivacyManager.StateTypes.ENABLED, captor.getValue().getState());
} finally {
mockitoSession.finishMocking();
}
onDeviceFile.delete();
}
private SensorPrivacyStateController getSensorPrivacyStateControllerImpl() {
SensorPrivacyStateControllerImpl.getInstance().resetForTestingImpl();
return SensorPrivacyStateControllerImpl.getInstance();
}
}