Merge "Refactor SensorPrivacyService to separate state from service"
This commit is contained in:
committed by
Android (Google) Code Review
commit
e5c3230597
@@ -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
|
||||
*
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
include platform/frameworks/native:/libs/sensorprivacy/OWNERS
|
||||
@@ -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<>();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -183,6 +183,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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user