Merge changes I41372c25,I4472e23a
* changes: Add support for sensor conditions in device state configuration file. Update DeviceStateProviderImpl to read from device state config file.
This commit is contained in:
committed by
Android (Google) Code Review
commit
47f37a2ad8
@@ -75,6 +75,7 @@ java_library_static {
|
||||
":platform-compat-config",
|
||||
":display-device-config",
|
||||
":cec-config",
|
||||
":device-state-config",
|
||||
"java/com/android/server/EventLogTags.logtags",
|
||||
"java/com/android/server/am/EventLogTags.logtags",
|
||||
"java/com/android/server/wm/EventLogTags.logtags",
|
||||
|
||||
@@ -100,7 +100,7 @@ public final class DeviceStateManagerService extends SystemService {
|
||||
private final SparseArray<CallbackRecord> mCallbacks = new SparseArray<>();
|
||||
|
||||
public DeviceStateManagerService(@NonNull Context context) {
|
||||
this(context, new DeviceStatePolicyImpl());
|
||||
this(context, new DeviceStatePolicyImpl(context));
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package com.android.server.policy;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.content.Context;
|
||||
|
||||
import com.android.server.devicestate.DeviceStatePolicy;
|
||||
import com.android.server.devicestate.DeviceStateProvider;
|
||||
@@ -27,10 +28,12 @@ import com.android.server.devicestate.DeviceStateProvider;
|
||||
* @see DeviceStateProviderImpl
|
||||
*/
|
||||
public final class DeviceStatePolicyImpl implements DeviceStatePolicy {
|
||||
private final Context mContext;
|
||||
private final DeviceStateProvider mProvider;
|
||||
|
||||
public DeviceStatePolicyImpl() {
|
||||
mProvider = new DeviceStateProviderImpl();
|
||||
public DeviceStatePolicyImpl(Context context) {
|
||||
mContext = context;
|
||||
mProvider = DeviceStateProviderImpl.create(mContext);
|
||||
}
|
||||
|
||||
public DeviceStateProvider getDeviceStateProvider() {
|
||||
|
||||
@@ -16,30 +16,464 @@
|
||||
|
||||
package com.android.server.policy;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.hardware.Sensor;
|
||||
import android.hardware.SensorEvent;
|
||||
import android.hardware.SensorEventListener;
|
||||
import android.hardware.SensorManager;
|
||||
import android.hardware.input.InputManagerInternal;
|
||||
import android.os.Environment;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Slog;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.devicestate.DeviceStateProvider;
|
||||
import com.android.server.policy.devicestate.config.Conditions;
|
||||
import com.android.server.policy.devicestate.config.DeviceState;
|
||||
import com.android.server.policy.devicestate.config.DeviceStateConfig;
|
||||
import com.android.server.policy.devicestate.config.LidSwitchCondition;
|
||||
import com.android.server.policy.devicestate.config.NumericRange;
|
||||
import com.android.server.policy.devicestate.config.SensorCondition;
|
||||
import com.android.server.policy.devicestate.config.XmlParser;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.BooleanSupplier;
|
||||
|
||||
import javax.xml.datatype.DatatypeConfigurationException;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link DeviceStateProvider}. Currently only supports
|
||||
* {@link #DEFAULT_DEVICE_STATE}.
|
||||
*
|
||||
* @see DeviceStatePolicyImpl
|
||||
* Implementation of {@link DeviceStateProvider} that reads the set of supported device states
|
||||
* from a configuration file provided at either /vendor/etc/devicestate or
|
||||
* /data/system/devicestate/. By default, the provider supports {@link #DEFAULT_DEVICE_STATE} when
|
||||
* no configuration is provided.
|
||||
*/
|
||||
final class DeviceStateProviderImpl implements DeviceStateProvider {
|
||||
private static final int DEFAULT_DEVICE_STATE = 0;
|
||||
public final class DeviceStateProviderImpl implements DeviceStateProvider,
|
||||
InputManagerInternal.LidSwitchCallback, SensorEventListener {
|
||||
private static final String TAG = "DeviceStateProviderImpl";
|
||||
|
||||
private static final BooleanSupplier TRUE_BOOLEAN_SUPPLIER = () -> true;
|
||||
|
||||
@VisibleForTesting
|
||||
static final int DEFAULT_DEVICE_STATE = 0;
|
||||
|
||||
private static final String VENDOR_CONFIG_FILE_PATH = "etc/devicestate/";
|
||||
private static final String DATA_CONFIG_FILE_PATH = "system/devicestate/";
|
||||
private static final String CONFIG_FILE_NAME = "device_state_configuration.xml";
|
||||
|
||||
/** Interface that allows reading the device state configuration. */
|
||||
interface ReadableConfig {
|
||||
@NonNull
|
||||
InputStream openRead() throws IOException;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@link DeviceStateProviderImpl} instance.
|
||||
*
|
||||
* @param context the {@link Context} that should be used to access system services.
|
||||
*/
|
||||
public static DeviceStateProviderImpl create(@NonNull Context context) {
|
||||
File configFile = getConfigurationFile();
|
||||
if (configFile == null) {
|
||||
return createFromConfig(context, null);
|
||||
}
|
||||
return createFromConfig(context, new ReadableFileConfig(configFile));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@link DeviceStateProviderImpl} instance.
|
||||
*
|
||||
* @param context the {@link Context} that should be used to access system services.
|
||||
* @param readableConfig the config the provider instance should read supported states from.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static DeviceStateProviderImpl createFromConfig(@NonNull Context context,
|
||||
@Nullable ReadableConfig readableConfig) {
|
||||
SparseArray<Conditions> conditionsForState = new SparseArray<>();
|
||||
if (readableConfig != null) {
|
||||
DeviceStateConfig config = parseConfig(readableConfig);
|
||||
if (config != null) {
|
||||
for (DeviceState stateConfig : config.getDeviceState()) {
|
||||
int state = stateConfig.getIdentifier().intValue();
|
||||
Conditions conditions = stateConfig.getConditions();
|
||||
conditionsForState.put(state, conditions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (conditionsForState.size() == 0) {
|
||||
conditionsForState.put(DEFAULT_DEVICE_STATE, null);
|
||||
}
|
||||
return new DeviceStateProviderImpl(context, conditionsForState);
|
||||
}
|
||||
|
||||
// Lock for internal state.
|
||||
private final Object mLock = new Object();
|
||||
private final Context mContext;
|
||||
// List of supported states in ascending order.
|
||||
private final int[] mOrderedStates;
|
||||
// Map of state to a boolean supplier that returns true when all required conditions are met for
|
||||
// the device to be in the state.
|
||||
private final SparseArray<BooleanSupplier> mStateConditions;
|
||||
|
||||
@Nullable
|
||||
@GuardedBy("mLock")
|
||||
private Listener mListener = null;
|
||||
@GuardedBy("mLock")
|
||||
private int mLastReportedState = INVALID_DEVICE_STATE;
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private boolean mIsLidOpen;
|
||||
@GuardedBy("mLock")
|
||||
private final Map<Sensor, SensorEvent> mLatestSensorEvent = new ArrayMap<>();
|
||||
|
||||
private DeviceStateProviderImpl(@NonNull Context context,
|
||||
@NonNull SparseArray<Conditions> conditionsForState) {
|
||||
mContext = context;
|
||||
mOrderedStates = new int[conditionsForState.size()];
|
||||
for (int i = 0; i < conditionsForState.size(); i++) {
|
||||
mOrderedStates[i] = conditionsForState.keyAt(i);
|
||||
}
|
||||
|
||||
// Whether or not this instance should register to receive lid switch notifications from
|
||||
// InputManagerInternal. If there are no device state conditions that are based on the lid
|
||||
// switch there is no need to register for a callback.
|
||||
boolean shouldListenToLidSwitch = false;
|
||||
|
||||
final SensorManager sensorManager = mContext.getSystemService(SensorManager.class);
|
||||
// The set of Sensor(s) that this instance should register to receive SensorEvent(s) from.
|
||||
final ArraySet<Sensor> sensorsToListenTo = new ArraySet<>();
|
||||
|
||||
mStateConditions = new SparseArray<>();
|
||||
for (int i = 0; i < mOrderedStates.length; i++) {
|
||||
int state = mOrderedStates[i];
|
||||
Conditions conditions = conditionsForState.get(state);
|
||||
if (conditions == null) {
|
||||
mStateConditions.put(state, TRUE_BOOLEAN_SUPPLIER);
|
||||
continue;
|
||||
}
|
||||
|
||||
List<BooleanSupplier> suppliers = new ArrayList<>();
|
||||
|
||||
LidSwitchCondition lidSwitchCondition = conditions.getLidSwitch();
|
||||
if (lidSwitchCondition != null) {
|
||||
suppliers.add(new LidSwitchBooleanSupplier(lidSwitchCondition.getOpen()));
|
||||
shouldListenToLidSwitch = true;
|
||||
}
|
||||
|
||||
List<SensorCondition> sensorConditions = conditions.getSensor();
|
||||
for (int j = 0; j < sensorConditions.size(); j++) {
|
||||
SensorCondition sensorCondition = sensorConditions.get(j);
|
||||
final int expectedSensorType = sensorCondition.getType().intValue();
|
||||
final String expectedSensorName = sensorCondition.getName();
|
||||
|
||||
List<Sensor> sensors = sensorManager.getSensorList(expectedSensorType);
|
||||
Sensor foundSensor = null;
|
||||
for (int sensorIndex = 0; sensorIndex < sensors.size(); sensorIndex++) {
|
||||
Sensor sensor = sensors.get(sensorIndex);
|
||||
if (sensor.getName().equals(expectedSensorName)) {
|
||||
foundSensor = sensor;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (foundSensor == null) {
|
||||
throw new IllegalStateException("Failed to find Sensor with type: "
|
||||
+ expectedSensorType + " and name: " + expectedSensorName);
|
||||
}
|
||||
|
||||
suppliers.add(new SensorBooleanSupplier(foundSensor, sensorCondition.getValue()));
|
||||
sensorsToListenTo.add(foundSensor);
|
||||
}
|
||||
|
||||
if (suppliers.size() > 1) {
|
||||
mStateConditions.put(state, new AndBooleanSupplier(suppliers));
|
||||
} else if (suppliers.size() > 0) {
|
||||
// No need to wrap with an AND supplier if there is only 1.
|
||||
mStateConditions.put(state, suppliers.get(0));
|
||||
} else {
|
||||
// There are no conditions for this state. Default to always true.
|
||||
mStateConditions.put(state, TRUE_BOOLEAN_SUPPLIER);
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldListenToLidSwitch) {
|
||||
InputManagerInternal inputManager = LocalServices.getService(
|
||||
InputManagerInternal.class);
|
||||
inputManager.registerLidSwitchCallback(this);
|
||||
}
|
||||
|
||||
for (int i = 0; i < sensorsToListenTo.size(); i++) {
|
||||
Sensor sensor = sensorsToListenTo.valueAt(i);
|
||||
sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_FASTEST);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setListener(Listener listener) {
|
||||
if (mListener != null) {
|
||||
throw new RuntimeException("Provider already has a listener set.");
|
||||
synchronized (mLock) {
|
||||
if (mListener != null) {
|
||||
throw new RuntimeException("Provider already has a listener set.");
|
||||
}
|
||||
mListener = listener;
|
||||
}
|
||||
notifySupportedStatesChanged();
|
||||
notifyDeviceStateChangedIfNeeded();
|
||||
}
|
||||
|
||||
/** Notifies the listener that the set of supported device states has changed. */
|
||||
private void notifySupportedStatesChanged() {
|
||||
int[] supportedStates;
|
||||
synchronized (mLock) {
|
||||
if (mListener == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
supportedStates = Arrays.copyOf(mOrderedStates, mOrderedStates.length);
|
||||
}
|
||||
|
||||
mListener = listener;
|
||||
mListener.onSupportedDeviceStatesChanged(new int[]{ DEFAULT_DEVICE_STATE });
|
||||
mListener.onStateChanged(DEFAULT_DEVICE_STATE);
|
||||
mListener.onSupportedDeviceStatesChanged(supportedStates);
|
||||
}
|
||||
|
||||
/** Computes the current device state and notifies the listener of a change, if needed. */
|
||||
void notifyDeviceStateChangedIfNeeded() {
|
||||
int stateToReport = INVALID_DEVICE_STATE;
|
||||
synchronized (mLock) {
|
||||
if (mListener == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int newState = mOrderedStates[0];
|
||||
for (int i = 1; i < mOrderedStates.length; i++) {
|
||||
int state = mOrderedStates[i];
|
||||
if (mStateConditions.get(state).getAsBoolean()) {
|
||||
newState = state;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (newState != mLastReportedState) {
|
||||
mLastReportedState = newState;
|
||||
stateToReport = newState;
|
||||
}
|
||||
}
|
||||
|
||||
if (stateToReport != INVALID_DEVICE_STATE) {
|
||||
mListener.onStateChanged(stateToReport);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen) {
|
||||
synchronized (mLock) {
|
||||
mIsLidOpen = lidOpen;
|
||||
}
|
||||
notifyDeviceStateChangedIfNeeded();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSensorChanged(SensorEvent event) {
|
||||
synchronized (mLock) {
|
||||
mLatestSensorEvent.put(event.sensor, event);
|
||||
}
|
||||
notifyDeviceStateChangedIfNeeded();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAccuracyChanged(Sensor sensor, int accuracy) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of {@link BooleanSupplier} that returns {@code true} if the expected lid
|
||||
* switch open state matches {@link #mIsLidOpen}.
|
||||
*/
|
||||
private final class LidSwitchBooleanSupplier implements BooleanSupplier {
|
||||
private final boolean mExpectedOpen;
|
||||
|
||||
LidSwitchBooleanSupplier(boolean expectedOpen) {
|
||||
mExpectedOpen = expectedOpen;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getAsBoolean() {
|
||||
synchronized (mLock) {
|
||||
return mIsLidOpen == mExpectedOpen;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of {@link BooleanSupplier} that returns {@code true} if the latest
|
||||
* {@link SensorEvent#values sensor event values} for the specified {@link Sensor} adhere to
|
||||
* the supplied {@link NumericRange ranges}.
|
||||
*/
|
||||
private final class SensorBooleanSupplier implements BooleanSupplier {
|
||||
@NonNull
|
||||
private final Sensor mSensor;
|
||||
@NonNull
|
||||
private final List<NumericRange> mExpectedValues;
|
||||
|
||||
SensorBooleanSupplier(@NonNull Sensor sensor, @NonNull List<NumericRange> expectedValues) {
|
||||
mSensor = sensor;
|
||||
mExpectedValues = expectedValues;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getAsBoolean() {
|
||||
synchronized (mLock) {
|
||||
SensorEvent latestEvent = mLatestSensorEvent.get(mSensor);
|
||||
if (latestEvent == null) {
|
||||
// Default to returning false if we have not yet received a sensor event for the
|
||||
// sensor.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (latestEvent.values.length != mExpectedValues.size()) {
|
||||
throw new IllegalStateException("Number of supplied numeric range(s) does not "
|
||||
+ "match the number of values in the latest sensor event for sensor: "
|
||||
+ mSensor);
|
||||
}
|
||||
|
||||
for (int i = 0; i < latestEvent.values.length; i++) {
|
||||
if (!adheresToRange(latestEvent.values[i], mExpectedValues.get(i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the supplied {@code value} adheres to the constraints specified
|
||||
* in {@code range}.
|
||||
*/
|
||||
private boolean adheresToRange(float value, @NonNull NumericRange range) {
|
||||
final BigDecimal min = range.getMin_optional();
|
||||
if (min != null) {
|
||||
if (value <= min.floatValue()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
final BigDecimal minInclusive = range.getMinInclusive_optional();
|
||||
if (minInclusive != null) {
|
||||
if (value < minInclusive.floatValue()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
final BigDecimal max = range.getMax_optional();
|
||||
if (max != null) {
|
||||
if (value >= max.floatValue()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
final BigDecimal maxInclusive = range.getMaxInclusive_optional();
|
||||
if (maxInclusive != null) {
|
||||
if (value > maxInclusive.floatValue()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of {@link BooleanSupplier} whose result is the product of an AND operation
|
||||
* applied to the result of all child suppliers.
|
||||
*/
|
||||
private static final class AndBooleanSupplier implements BooleanSupplier {
|
||||
@NonNull
|
||||
List<BooleanSupplier> mBooleanSuppliers;
|
||||
|
||||
AndBooleanSupplier(@NonNull List<BooleanSupplier> booleanSuppliers) {
|
||||
mBooleanSuppliers = booleanSuppliers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getAsBoolean() {
|
||||
for (int i = 0; i < mBooleanSuppliers.size(); i++) {
|
||||
if (!mBooleanSuppliers.get(i).getAsBoolean()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the device state configuration file that should be used, or {@code null} if no file
|
||||
* is present on the device.
|
||||
* <p>
|
||||
* Defaults to returning a config file present in the data/ dir at
|
||||
* {@link #DATA_CONFIG_FILE_PATH}, and then falls back to the config file in the vendor/ dir
|
||||
* at {@link #VENDOR_CONFIG_FILE_PATH} if no config file is found in the data/ dir.
|
||||
*/
|
||||
@Nullable
|
||||
private static File getConfigurationFile() {
|
||||
final File configFileFromDataDir = Environment.buildPath(Environment.getDataDirectory(),
|
||||
DATA_CONFIG_FILE_PATH, CONFIG_FILE_NAME);
|
||||
if (configFileFromDataDir.exists()) {
|
||||
return configFileFromDataDir;
|
||||
}
|
||||
|
||||
final File configFileFromVendorDir = Environment.buildPath(Environment.getVendorDirectory(),
|
||||
VENDOR_CONFIG_FILE_PATH, CONFIG_FILE_NAME);
|
||||
if (configFileFromVendorDir.exists()) {
|
||||
return configFileFromVendorDir;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to parse the provided file into a {@link DeviceStateConfig} object. Returns
|
||||
* {@code null} if the file could not be successfully parsed.
|
||||
*/
|
||||
@Nullable
|
||||
private static DeviceStateConfig parseConfig(@NonNull ReadableConfig readableConfig) {
|
||||
try (InputStream in = readableConfig.openRead();
|
||||
InputStream bin = new BufferedInputStream(in)) {
|
||||
return XmlParser.read(bin);
|
||||
} catch (IOException | DatatypeConfigurationException | XmlPullParserException e) {
|
||||
Slog.e(TAG, "Encountered an error while reading device state config", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Implementation of {@link ReadableConfig} that reads config data from a file. */
|
||||
private static final class ReadableFileConfig implements ReadableConfig {
|
||||
@NonNull
|
||||
private final File mFile;
|
||||
|
||||
private ReadableFileConfig(@NonNull File file) {
|
||||
mFile = file;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream openRead() throws IOException {
|
||||
return new FileInputStream(mFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,3 +28,10 @@ xsd_config {
|
||||
api_dir: "cec-config/schema",
|
||||
package_name: "com.android.server.hdmi.cec.config",
|
||||
}
|
||||
|
||||
xsd_config {
|
||||
name: "device-state-config",
|
||||
srcs: ["device-state-config/device-state-config.xsd"],
|
||||
api_dir: "device-state-config/schema",
|
||||
package_name: "com.android.server.policy.devicestate.config",
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2020 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.
|
||||
-->
|
||||
|
||||
<xs:schema version="2.0"
|
||||
elementFormDefault="qualified"
|
||||
xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||
|
||||
<xs:element name="device-state-config">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="device-state" type="deviceState" maxOccurs="256" />
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
|
||||
<xs:complexType name="deviceState">
|
||||
<xs:sequence>
|
||||
<xs:element name="identifier">
|
||||
<xs:simpleType>
|
||||
<xs:restriction base="xs:integer">
|
||||
<xs:minInclusive value="0" />
|
||||
<xs:maxInclusive value="255" />
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
</xs:element>
|
||||
<xs:element name="name" type="xs:string" minOccurs="0" />
|
||||
<xs:element name="conditions" type="conditions" />
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="conditions">
|
||||
<xs:sequence>
|
||||
<xs:element name="lid-switch" type="lidSwitchCondition" minOccurs="0" />
|
||||
<xs:element name="sensor" type="sensorCondition" minOccurs="0" maxOccurs="unbounded" />
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="lidSwitchCondition">
|
||||
<xs:sequence>
|
||||
<xs:element name="open" type="xs:boolean" />
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="sensorCondition">
|
||||
<xs:sequence>
|
||||
<xs:element name="name" type="xs:string" />
|
||||
<xs:element name="type" type="xs:positiveInteger" />
|
||||
<xs:element name="value" type="numericRange" maxOccurs="unbounded" />
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="numericRange">
|
||||
<xs:sequence>
|
||||
<xs:choice minOccurs="0">
|
||||
<xs:element name="min" type="xs:decimal" />
|
||||
<xs:element name="min-inclusive" type="xs:decimal" />
|
||||
</xs:choice>
|
||||
<xs:choice minOccurs="0">
|
||||
<xs:element name="max" type="xs:decimal" />
|
||||
<xs:element name="max-inclusive" type="xs:decimal"/>
|
||||
</xs:choice>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:schema>
|
||||
61
services/core/xsd/device-state-config/schema/current.txt
Normal file
61
services/core/xsd/device-state-config/schema/current.txt
Normal file
@@ -0,0 +1,61 @@
|
||||
// Signature format: 2.0
|
||||
package com.android.server.policy.devicestate.config {
|
||||
|
||||
public class Conditions {
|
||||
ctor public Conditions();
|
||||
method public com.android.server.policy.devicestate.config.LidSwitchCondition getLidSwitch();
|
||||
method public java.util.List<com.android.server.policy.devicestate.config.SensorCondition> getSensor();
|
||||
method public void setLidSwitch(com.android.server.policy.devicestate.config.LidSwitchCondition);
|
||||
}
|
||||
|
||||
public class DeviceState {
|
||||
ctor public DeviceState();
|
||||
method public com.android.server.policy.devicestate.config.Conditions getConditions();
|
||||
method public java.math.BigInteger getIdentifier();
|
||||
method public String getName();
|
||||
method public void setConditions(com.android.server.policy.devicestate.config.Conditions);
|
||||
method public void setIdentifier(java.math.BigInteger);
|
||||
method public void setName(String);
|
||||
}
|
||||
|
||||
public class DeviceStateConfig {
|
||||
ctor public DeviceStateConfig();
|
||||
method public java.util.List<com.android.server.policy.devicestate.config.DeviceState> getDeviceState();
|
||||
}
|
||||
|
||||
public class LidSwitchCondition {
|
||||
ctor public LidSwitchCondition();
|
||||
method public boolean getOpen();
|
||||
method public void setOpen(boolean);
|
||||
}
|
||||
|
||||
public class NumericRange {
|
||||
ctor public NumericRange();
|
||||
method public java.math.BigDecimal getMaxInclusive_optional();
|
||||
method public java.math.BigDecimal getMax_optional();
|
||||
method public java.math.BigDecimal getMinInclusive_optional();
|
||||
method public java.math.BigDecimal getMin_optional();
|
||||
method public void setMaxInclusive_optional(java.math.BigDecimal);
|
||||
method public void setMax_optional(java.math.BigDecimal);
|
||||
method public void setMinInclusive_optional(java.math.BigDecimal);
|
||||
method public void setMin_optional(java.math.BigDecimal);
|
||||
}
|
||||
|
||||
public class SensorCondition {
|
||||
ctor public SensorCondition();
|
||||
method public String getName();
|
||||
method public java.math.BigInteger getType();
|
||||
method public java.util.List<com.android.server.policy.devicestate.config.NumericRange> getValue();
|
||||
method public void setName(String);
|
||||
method public void setType(java.math.BigInteger);
|
||||
}
|
||||
|
||||
public class XmlParser {
|
||||
ctor public XmlParser();
|
||||
method public static com.android.server.policy.devicestate.config.DeviceStateConfig read(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException;
|
||||
method public static String readText(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
|
||||
method public static void skip(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
1
services/core/xsd/device-state-config/schema/removed.txt
Normal file
1
services/core/xsd/device-state-config/schema/removed.txt
Normal file
@@ -0,0 +1 @@
|
||||
// Signature format: 2.0
|
||||
@@ -0,0 +1,289 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.policy;
|
||||
|
||||
|
||||
import static android.content.Context.SENSOR_SERVICE;
|
||||
|
||||
import static com.android.server.policy.DeviceStateProviderImpl.DEFAULT_DEVICE_STATE;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.Mockito.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.hardware.Sensor;
|
||||
import android.hardware.SensorEvent;
|
||||
import android.hardware.SensorManager;
|
||||
import android.hardware.input.InputManagerInternal;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.devicestate.DeviceStateProvider;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.internal.util.reflection.FieldSetter;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link DeviceStateProviderImpl}.
|
||||
* <p/>
|
||||
* Run with <code>atest DeviceStateProviderImplTest</code>.
|
||||
*/
|
||||
public final class DeviceStateProviderImplTest {
|
||||
private final ArgumentCaptor<int[]> mIntArrayCaptor = ArgumentCaptor.forClass(int[].class);
|
||||
private final ArgumentCaptor<Integer> mIntegerCaptor = ArgumentCaptor.forClass(Integer.class);
|
||||
|
||||
private Context mContext;
|
||||
private SensorManager mSensorManager;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
LocalServices.addService(InputManagerInternal.class, mock(InputManagerInternal.class));
|
||||
mContext = mock(Context.class);
|
||||
mSensorManager = mock(SensorManager.class);
|
||||
when(mContext.getSystemServiceName(eq(SensorManager.class))).thenReturn(SENSOR_SERVICE);
|
||||
when(mContext.getSystemService(eq(SENSOR_SERVICE))).thenReturn(mSensorManager);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
LocalServices.removeServiceForTest(InputManagerInternal.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void create_noConfig() {
|
||||
assertDefaultProviderValues(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void create_emptyFile() {
|
||||
String configString = "";
|
||||
DeviceStateProviderImpl.ReadableConfig config = new TestReadableConfig(configString);
|
||||
|
||||
assertDefaultProviderValues(config);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void create_emptyConfig() {
|
||||
String configString = "<device-state-config></device-state-config>";
|
||||
DeviceStateProviderImpl.ReadableConfig config = new TestReadableConfig(configString);
|
||||
|
||||
assertDefaultProviderValues(config);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void create_invalidConfig() {
|
||||
String configString = "<device-state-config>\n"
|
||||
+ " </device-state>\n"
|
||||
+ "</device-state-config>\n";
|
||||
DeviceStateProviderImpl.ReadableConfig config = new TestReadableConfig(configString);
|
||||
|
||||
assertDefaultProviderValues(config);
|
||||
}
|
||||
|
||||
private void assertDefaultProviderValues(
|
||||
@Nullable DeviceStateProviderImpl.ReadableConfig config) {
|
||||
DeviceStateProviderImpl provider = DeviceStateProviderImpl.createFromConfig(mContext,
|
||||
config);
|
||||
|
||||
DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
|
||||
provider.setListener(listener);
|
||||
|
||||
verify(listener).onSupportedDeviceStatesChanged(mIntArrayCaptor.capture());
|
||||
assertArrayEquals(new int[] { DEFAULT_DEVICE_STATE }, mIntArrayCaptor.getValue());
|
||||
|
||||
verify(listener).onStateChanged(mIntegerCaptor.capture());
|
||||
assertEquals(DEFAULT_DEVICE_STATE, mIntegerCaptor.getValue().intValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void create_lidSwitch() {
|
||||
String configString = "<device-state-config>\n"
|
||||
+ " <device-state>\n"
|
||||
+ " <identifier>1</identifier>\n"
|
||||
+ " <conditions>\n"
|
||||
+ " <lid-switch>\n"
|
||||
+ " <open>true</open>\n"
|
||||
+ " </lid-switch>\n"
|
||||
+ " </conditions>\n"
|
||||
+ " </device-state>\n"
|
||||
+ " <device-state>\n"
|
||||
+ " <identifier>2</identifier>\n"
|
||||
+ " <conditions>\n"
|
||||
+ " <lid-switch>\n"
|
||||
+ " <open>false</open>\n"
|
||||
+ " </lid-switch>\n"
|
||||
+ " </conditions>\n"
|
||||
+ " </device-state>\n"
|
||||
+ "</device-state-config>\n";
|
||||
DeviceStateProviderImpl.ReadableConfig config = new TestReadableConfig(configString);
|
||||
DeviceStateProviderImpl provider = DeviceStateProviderImpl.createFromConfig(mContext,
|
||||
config);
|
||||
|
||||
DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
|
||||
provider.setListener(listener);
|
||||
|
||||
verify(listener).onSupportedDeviceStatesChanged(mIntArrayCaptor.capture());
|
||||
assertArrayEquals(new int[] { 1, 2 }, mIntArrayCaptor.getValue());
|
||||
|
||||
verify(listener).onStateChanged(mIntegerCaptor.capture());
|
||||
assertEquals(2, mIntegerCaptor.getValue().intValue());
|
||||
|
||||
Mockito.clearInvocations(listener);
|
||||
|
||||
provider.notifyLidSwitchChanged(0, true /* lidOpen */);
|
||||
|
||||
verify(listener, never()).onSupportedDeviceStatesChanged(mIntArrayCaptor.capture());
|
||||
verify(listener).onStateChanged(mIntegerCaptor.capture());
|
||||
assertEquals(1, mIntegerCaptor.getValue().intValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void create_sensor() throws Exception {
|
||||
Sensor sensor = newSensor("sensor", Sensor.TYPE_HINGE_ANGLE);
|
||||
when(mSensorManager.getSensorList(eq(sensor.getType()))).thenReturn(List.of(sensor));
|
||||
|
||||
String configString = "<device-state-config>\n"
|
||||
+ " <device-state>\n"
|
||||
+ " <identifier>1</identifier>\n"
|
||||
+ " <conditions>\n"
|
||||
+ " <sensor>\n"
|
||||
+ " <name>" + sensor.getName() + "</name>\n"
|
||||
+ " <type>" + sensor.getType() + "</type>\n"
|
||||
+ " <value>\n"
|
||||
+ " <max>90</max>\n"
|
||||
+ " </value>\n"
|
||||
+ " </sensor>\n"
|
||||
+ " </conditions>\n"
|
||||
+ " </device-state>\n"
|
||||
+ " <device-state>\n"
|
||||
+ " <identifier>2</identifier>\n"
|
||||
+ " <conditions>\n"
|
||||
+ " <sensor>\n"
|
||||
+ " <name>" + sensor.getName() + "</name>\n"
|
||||
+ " <type>" + sensor.getType() + "</type>\n"
|
||||
+ " <value>\n"
|
||||
+ " <min-inclusive>90</min-inclusive>\n"
|
||||
+ " <max>180</max>\n"
|
||||
+ " </value>\n"
|
||||
+ " </sensor>\n"
|
||||
+ " </conditions>\n"
|
||||
+ " </device-state>\n"
|
||||
+ " <device-state>\n"
|
||||
+ " <identifier>3</identifier>\n"
|
||||
+ " <conditions>\n"
|
||||
+ " <sensor>\n"
|
||||
+ " <name>" + sensor.getName() + "</name>\n"
|
||||
+ " <type>" + sensor.getType() + "</type>\n"
|
||||
+ " <value>\n"
|
||||
+ " <min-inclusive>180</min-inclusive>\n"
|
||||
+ " </value>\n"
|
||||
+ " </sensor>\n"
|
||||
+ " </conditions>\n"
|
||||
+ " </device-state>\n"
|
||||
+ "</device-state-config>\n";
|
||||
DeviceStateProviderImpl.ReadableConfig config = new TestReadableConfig(configString);
|
||||
DeviceStateProviderImpl provider = DeviceStateProviderImpl.createFromConfig(mContext,
|
||||
config);
|
||||
|
||||
DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
|
||||
provider.setListener(listener);
|
||||
|
||||
verify(listener).onSupportedDeviceStatesChanged(mIntArrayCaptor.capture());
|
||||
assertArrayEquals(new int[] { 1, 2, 3 }, mIntArrayCaptor.getValue());
|
||||
|
||||
verify(listener).onStateChanged(mIntegerCaptor.capture());
|
||||
assertEquals(1, mIntegerCaptor.getValue().intValue());
|
||||
|
||||
Mockito.clearInvocations(listener);
|
||||
|
||||
SensorEvent event0 = mock(SensorEvent.class);
|
||||
event0.sensor = sensor;
|
||||
FieldSetter.setField(event0, event0.getClass().getField("values"), new float[] { 180 });
|
||||
|
||||
provider.onSensorChanged(event0);
|
||||
|
||||
verify(listener, never()).onSupportedDeviceStatesChanged(mIntArrayCaptor.capture());
|
||||
verify(listener).onStateChanged(mIntegerCaptor.capture());
|
||||
assertEquals(3, mIntegerCaptor.getValue().intValue());
|
||||
|
||||
Mockito.clearInvocations(listener);
|
||||
|
||||
SensorEvent event1 = mock(SensorEvent.class);
|
||||
event1.sensor = sensor;
|
||||
FieldSetter.setField(event1, event1.getClass().getField("values"), new float[] { 90 });
|
||||
|
||||
provider.onSensorChanged(event1);
|
||||
|
||||
verify(listener, never()).onSupportedDeviceStatesChanged(mIntArrayCaptor.capture());
|
||||
verify(listener).onStateChanged(mIntegerCaptor.capture());
|
||||
assertEquals(2, mIntegerCaptor.getValue().intValue());
|
||||
|
||||
Mockito.clearInvocations(listener);
|
||||
|
||||
SensorEvent event2 = mock(SensorEvent.class);
|
||||
event2.sensor = sensor;
|
||||
FieldSetter.setField(event2, event2.getClass().getField("values"), new float[] { 0 });
|
||||
|
||||
provider.onSensorChanged(event2);
|
||||
|
||||
verify(listener, never()).onSupportedDeviceStatesChanged(mIntArrayCaptor.capture());
|
||||
verify(listener).onStateChanged(mIntegerCaptor.capture());
|
||||
assertEquals(1, mIntegerCaptor.getValue().intValue());
|
||||
}
|
||||
|
||||
private static Sensor newSensor(String name, int type) throws Exception {
|
||||
Constructor<Sensor> constructor = Sensor.class.getDeclaredConstructor();
|
||||
constructor.setAccessible(true);
|
||||
|
||||
Sensor sensor = constructor.newInstance();
|
||||
FieldSetter.setField(sensor, Sensor.class.getDeclaredField("mName"), name);
|
||||
FieldSetter.setField(sensor, Sensor.class.getDeclaredField("mType"), type);
|
||||
return sensor;
|
||||
}
|
||||
|
||||
private static final class TestReadableConfig implements
|
||||
DeviceStateProviderImpl.ReadableConfig {
|
||||
private final byte[] mData;
|
||||
|
||||
TestReadableConfig(String configFileData) {
|
||||
mData = configFileData.getBytes();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public InputStream openRead() throws IOException {
|
||||
return new ByteArrayInputStream(mData);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user