• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.policy;
18 
19 import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
20 import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
21 
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.content.Context;
25 import android.hardware.Sensor;
26 import android.hardware.SensorEvent;
27 import android.hardware.SensorEventListener;
28 import android.hardware.SensorManager;
29 import android.hardware.input.InputManagerInternal;
30 import android.os.Environment;
31 import android.util.ArrayMap;
32 import android.util.ArraySet;
33 import android.util.Slog;
34 import android.util.SparseArray;
35 
36 import com.android.internal.annotations.GuardedBy;
37 import com.android.internal.annotations.VisibleForTesting;
38 import com.android.internal.util.Preconditions;
39 import com.android.server.LocalServices;
40 import com.android.server.devicestate.DeviceState;
41 import com.android.server.devicestate.DeviceStateProvider;
42 import com.android.server.policy.devicestate.config.Conditions;
43 import com.android.server.policy.devicestate.config.DeviceStateConfig;
44 import com.android.server.policy.devicestate.config.Flags;
45 import com.android.server.policy.devicestate.config.LidSwitchCondition;
46 import com.android.server.policy.devicestate.config.NumericRange;
47 import com.android.server.policy.devicestate.config.SensorCondition;
48 import com.android.server.policy.devicestate.config.XmlParser;
49 
50 import org.xmlpull.v1.XmlPullParserException;
51 
52 import java.io.BufferedInputStream;
53 import java.io.File;
54 import java.io.FileInputStream;
55 import java.io.IOException;
56 import java.io.InputStream;
57 import java.math.BigDecimal;
58 import java.util.ArrayList;
59 import java.util.Arrays;
60 import java.util.Comparator;
61 import java.util.List;
62 import java.util.Map;
63 import java.util.function.BooleanSupplier;
64 
65 import javax.xml.datatype.DatatypeConfigurationException;
66 
67 /**
68  * Implementation of {@link DeviceStateProvider} that reads the set of supported device states
69  * from a configuration file provided at either /vendor/etc/devicestate or
70  * /data/system/devicestate/.
71  * <p>
72  * When a device state configuration file is present this provider will consider the provided
73  * {@link Conditions} block for each declared state, halting and returning when the first set of
74  * conditions for a device state match the current system state. If there are multiple states whose
75  * conditions match the current system state the matching state with the smallest integer identifier
76  * will be returned. When no declared state matches the current system state, the device state with
77  * the smallest integer identifier will be returned.
78  * <p>
79  * By default, the provider reports {@link #DEFAULT_DEVICE_STATE} when no configuration file is
80  * provided.
81  */
82 public final class DeviceStateProviderImpl implements DeviceStateProvider,
83         InputManagerInternal.LidSwitchCallback, SensorEventListener {
84     private static final String TAG = "DeviceStateProviderImpl";
85     private static final boolean DEBUG = false;
86 
87     private static final BooleanSupplier TRUE_BOOLEAN_SUPPLIER = () -> true;
88     private static final BooleanSupplier FALSE_BOOLEAN_SUPPLIER = () -> false;
89 
90     @VisibleForTesting
91     static final DeviceState DEFAULT_DEVICE_STATE = new DeviceState(MINIMUM_DEVICE_STATE,
92             "DEFAULT", 0 /* flags */);
93 
94     private static final String VENDOR_CONFIG_FILE_PATH = "etc/devicestate/";
95     private static final String DATA_CONFIG_FILE_PATH = "system/devicestate/";
96     private static final String CONFIG_FILE_NAME = "device_state_configuration.xml";
97     private static final String FLAG_CANCEL_OVERRIDE_REQUESTS = "FLAG_CANCEL_OVERRIDE_REQUESTS";
98     private static final String FLAG_APP_INACCESSIBLE = "FLAG_APP_INACCESSIBLE";
99     private static final String FLAG_EMULATED_ONLY = "FLAG_EMULATED_ONLY";
100 
101     /** Interface that allows reading the device state configuration. */
102     interface ReadableConfig {
103         @NonNull
openRead()104         InputStream openRead() throws IOException;
105     }
106 
107     /**
108      * Returns a new {@link DeviceStateProviderImpl} instance.
109      *
110      * @param context the {@link Context} that should be used to access system services.
111      */
create(@onNull Context context)112     public static DeviceStateProviderImpl create(@NonNull Context context) {
113         File configFile = getConfigurationFile();
114         if (configFile == null) {
115             return createFromConfig(context, null);
116         }
117         return createFromConfig(context, new ReadableFileConfig(configFile));
118     }
119 
120     /**
121      * Returns a new {@link DeviceStateProviderImpl} instance.
122      *
123      * @param context the {@link Context} that should be used to access system services.
124      * @param readableConfig the config the provider instance should read supported states from.
125      */
126     @VisibleForTesting
createFromConfig(@onNull Context context, @Nullable ReadableConfig readableConfig)127     static DeviceStateProviderImpl createFromConfig(@NonNull Context context,
128             @Nullable ReadableConfig readableConfig) {
129         List<DeviceState> deviceStateList = new ArrayList<>();
130         List<Conditions> conditionsList = new ArrayList<>();
131 
132         if (readableConfig != null) {
133             DeviceStateConfig config = parseConfig(readableConfig);
134             if (config != null) {
135                 for (com.android.server.policy.devicestate.config.DeviceState stateConfig :
136                         config.getDeviceState()) {
137                     final int state = stateConfig.getIdentifier().intValue();
138                     final String name = stateConfig.getName() == null ? "" : stateConfig.getName();
139 
140                     int flags = 0;
141                     final Flags configFlags = stateConfig.getFlags();
142                     if (configFlags != null) {
143                         List<String> configFlagStrings = configFlags.getFlag();
144                         for (int i = 0; i < configFlagStrings.size(); i++) {
145                             final String configFlagString = configFlagStrings.get(i);
146                             switch (configFlagString) {
147                                 case FLAG_CANCEL_OVERRIDE_REQUESTS:
148                                     flags |= DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS;
149                                     break;
150                                 case FLAG_APP_INACCESSIBLE:
151                                     flags |= DeviceState.FLAG_APP_INACCESSIBLE;
152                                     break;
153                                 case FLAG_EMULATED_ONLY:
154                                     flags |= DeviceState.FLAG_EMULATED_ONLY;
155                                 default:
156                                     Slog.w(TAG, "Parsed unknown flag with name: "
157                                             + configFlagString);
158                                     break;
159                             }
160                         }
161                     }
162 
163                     deviceStateList.add(new DeviceState(state, name, flags));
164 
165                     final Conditions condition = stateConfig.getConditions();
166                     conditionsList.add(condition);
167                 }
168             }
169         }
170 
171         if (deviceStateList.size() == 0) {
172             deviceStateList.add(DEFAULT_DEVICE_STATE);
173             conditionsList.add(null);
174         }
175         return new DeviceStateProviderImpl(context, deviceStateList, conditionsList);
176     }
177 
178     // Lock for internal state.
179     private final Object mLock = new Object();
180     private final Context mContext;
181     // List of supported states in ascending order based on their identifier.
182     private final DeviceState[] mOrderedStates;
183     // Map of state identifier to a boolean supplier that returns true when all required conditions
184     // are met for the device to be in the state.
185     private final SparseArray<BooleanSupplier> mStateConditions = new SparseArray<>();
186 
187     @Nullable
188     @GuardedBy("mLock")
189     private Listener mListener = null;
190     @GuardedBy("mLock")
191     private int mLastReportedState = INVALID_DEVICE_STATE;
192 
193     @GuardedBy("mLock")
194     private Boolean mIsLidOpen;
195     @GuardedBy("mLock")
196     private final Map<Sensor, SensorEvent> mLatestSensorEvent = new ArrayMap<>();
197 
DeviceStateProviderImpl(@onNull Context context, @NonNull List<DeviceState> deviceStates, @NonNull List<Conditions> stateConditions)198     private DeviceStateProviderImpl(@NonNull Context context,
199             @NonNull List<DeviceState> deviceStates,
200             @NonNull List<Conditions> stateConditions) {
201         Preconditions.checkArgument(deviceStates.size() == stateConditions.size(),
202                 "Number of device states must be equal to the number of device state conditions.");
203 
204         mContext = context;
205 
206         DeviceState[] orderedStates = deviceStates.toArray(new DeviceState[deviceStates.size()]);
207         Arrays.sort(orderedStates, Comparator.comparingInt(DeviceState::getIdentifier));
208         mOrderedStates = orderedStates;
209 
210         setStateConditions(deviceStates, stateConditions);
211     }
212 
setStateConditions(@onNull List<DeviceState> deviceStates, @NonNull List<Conditions> stateConditions)213     private void setStateConditions(@NonNull List<DeviceState> deviceStates,
214             @NonNull List<Conditions> stateConditions) {
215         // Whether or not this instance should register to receive lid switch notifications from
216         // InputManagerInternal. If there are no device state conditions that are based on the lid
217         // switch there is no need to register for a callback.
218         boolean shouldListenToLidSwitch = false;
219 
220         // The set of Sensor(s) that this instance should register to receive SensorEvent(s) from.
221         final ArraySet<Sensor> sensorsToListenTo = new ArraySet<>();
222 
223         for (int i = 0; i < stateConditions.size(); i++) {
224             final int state = deviceStates.get(i).getIdentifier();
225             if (DEBUG) {
226                 Slog.d(TAG, "Evaluating conditions for device state " + state
227                         + " (" + deviceStates.get(i).getName() + ")");
228             }
229             final Conditions conditions = stateConditions.get(i);
230             if (conditions == null) {
231                 // If this state has the FLAG_EMULATED_ONLY flag on it, it should never be triggered
232                 // by a physical hardware change, and should always return false for it's conditions
233                 if (deviceStates.get(i).hasFlag(DeviceState.FLAG_EMULATED_ONLY)) {
234                     mStateConditions.put(state, FALSE_BOOLEAN_SUPPLIER);
235                 } else {
236                     mStateConditions.put(state, TRUE_BOOLEAN_SUPPLIER);
237                 }
238                 continue;
239             }
240 
241             // Whether or not all the required hardware components could be found that match the
242             // requirements from the config.
243             boolean allRequiredComponentsFound = true;
244             // Whether or not this condition requires the lid switch.
245             boolean lidSwitchRequired = false;
246             // Set of sensors required for this condition.
247             ArraySet<Sensor> sensorsRequired = new ArraySet<>();
248 
249             List<BooleanSupplier> suppliers = new ArrayList<>();
250 
251             LidSwitchCondition lidSwitchCondition = conditions.getLidSwitch();
252             if (lidSwitchCondition != null) {
253                 suppliers.add(new LidSwitchBooleanSupplier(lidSwitchCondition.getOpen()));
254                 lidSwitchRequired = true;
255                 if (DEBUG) {
256                     Slog.d(TAG, "Lid switch required");
257                 }
258             }
259 
260             List<SensorCondition> sensorConditions = conditions.getSensor();
261             for (int j = 0; j < sensorConditions.size(); j++) {
262                 SensorCondition sensorCondition = sensorConditions.get(j);
263                 final String expectedSensorType = sensorCondition.getType();
264                 final String expectedSensorName = sensorCondition.getName();
265 
266                 final Sensor foundSensor = findSensor(expectedSensorType, expectedSensorName);
267                 if (foundSensor == null) {
268                     Slog.e(TAG, "Failed to find Sensor with type: " + expectedSensorType
269                             + " and name: " + expectedSensorName);
270                     allRequiredComponentsFound = false;
271                     break;
272                 }
273 
274                 if (DEBUG) {
275                     Slog.d(TAG, "Found sensor with type: " + expectedSensorType
276                             + " (" + expectedSensorName + ")");
277                 }
278 
279                 suppliers.add(new SensorBooleanSupplier(foundSensor, sensorCondition.getValue()));
280                 sensorsRequired.add(foundSensor);
281             }
282 
283             if (allRequiredComponentsFound) {
284                 shouldListenToLidSwitch |= lidSwitchRequired;
285                 sensorsToListenTo.addAll(sensorsRequired);
286 
287                 if (suppliers.size() > 1) {
288                     mStateConditions.put(state, new AndBooleanSupplier(suppliers));
289                 } else if (suppliers.size() > 0) {
290                     // No need to wrap with an AND supplier if there is only 1.
291                     mStateConditions.put(state, suppliers.get(0));
292                 } else {
293                     // There are no conditions for this state. Default to always true.
294                     mStateConditions.put(state, TRUE_BOOLEAN_SUPPLIER);
295                 }
296             } else {
297                 // Failed to setup this condition. This can happen if a sensor is missing. Default
298                 // this state to always false.
299                 mStateConditions.put(state, FALSE_BOOLEAN_SUPPLIER);
300             }
301         }
302 
303         if (shouldListenToLidSwitch) {
304             InputManagerInternal inputManager = LocalServices.getService(
305                     InputManagerInternal.class);
306             inputManager.registerLidSwitchCallback(this);
307         }
308 
309         final SensorManager sensorManager = mContext.getSystemService(SensorManager.class);
310         for (int i = 0; i < sensorsToListenTo.size(); i++) {
311             Sensor sensor = sensorsToListenTo.valueAt(i);
312             sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_FASTEST);
313         }
314     }
315 
316     @Nullable
findSensor(String type, String name)317     private Sensor findSensor(String type, String name) {
318         final SensorManager sensorManager = mContext.getSystemService(SensorManager.class);
319         final List<Sensor> sensors = sensorManager.getSensorList(Sensor.TYPE_ALL);
320         for (int sensorIndex = 0; sensorIndex < sensors.size(); sensorIndex++) {
321             final Sensor sensor = sensors.get(sensorIndex);
322             final String sensorType = sensor.getStringType();
323             final String sensorName = sensor.getName();
324 
325             if (sensorType == null || sensorName == null) {
326                 continue;
327             }
328 
329             if (sensorType.equals(type) && sensorName.equals(name)) {
330                 return sensor;
331             }
332         }
333         return null;
334     }
335 
336     @Override
setListener(Listener listener)337     public void setListener(Listener listener) {
338         synchronized (mLock) {
339             if (mListener != null) {
340                 throw new RuntimeException("Provider already has a listener set.");
341             }
342             mListener = listener;
343         }
344         notifySupportedStatesChanged();
345         notifyDeviceStateChangedIfNeeded();
346     }
347 
348     /** Notifies the listener that the set of supported device states has changed. */
notifySupportedStatesChanged()349     private void notifySupportedStatesChanged() {
350         DeviceState[] supportedStates;
351         synchronized (mLock) {
352             if (mListener == null) {
353                 return;
354             }
355 
356             supportedStates = Arrays.copyOf(mOrderedStates, mOrderedStates.length);
357         }
358 
359         mListener.onSupportedDeviceStatesChanged(supportedStates);
360     }
361 
362     /** Computes the current device state and notifies the listener of a change, if needed. */
notifyDeviceStateChangedIfNeeded()363     void notifyDeviceStateChangedIfNeeded() {
364         int stateToReport = INVALID_DEVICE_STATE;
365         synchronized (mLock) {
366             if (mListener == null) {
367                 return;
368             }
369 
370             int newState = INVALID_DEVICE_STATE;
371             for (int i = 0; i < mOrderedStates.length; i++) {
372                 int state = mOrderedStates[i].getIdentifier();
373                 if (DEBUG) {
374                     Slog.d(TAG, "Checking conditions for " + mOrderedStates[i].getName() + "("
375                             + i + ")");
376                 }
377                 boolean conditionSatisfied;
378                 try {
379                     conditionSatisfied = mStateConditions.get(state).getAsBoolean();
380                 } catch (IllegalStateException e) {
381                     // Failed to compute the current state based on current available data. Continue
382                     // with the expectation that notifyDeviceStateChangedIfNeeded() will be called
383                     // when a callback with the missing data is triggered. May trigger another state
384                     // change if another state is satisfied currently.
385                     if (DEBUG) {
386                         Slog.d(TAG, "Unable to check current state", e);
387                     }
388                     continue;
389                 }
390 
391                 if (conditionSatisfied) {
392                     if (DEBUG) {
393                         Slog.d(TAG, "Device State conditions satisfied, transition to " + state);
394                     }
395                     newState = state;
396                     break;
397                 }
398             }
399             if (newState == INVALID_DEVICE_STATE) {
400                 Slog.e(TAG, "No declared device states match any of the required conditions.");
401                 dumpSensorValues();
402             }
403 
404             if (newState != INVALID_DEVICE_STATE && newState != mLastReportedState) {
405                 mLastReportedState = newState;
406                 stateToReport = newState;
407             }
408         }
409 
410         if (stateToReport != INVALID_DEVICE_STATE) {
411             mListener.onStateChanged(stateToReport);
412         }
413     }
414 
415     @Override
notifyLidSwitchChanged(long whenNanos, boolean lidOpen)416     public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen) {
417         synchronized (mLock) {
418             mIsLidOpen = lidOpen;
419         }
420         if (DEBUG) {
421             Slog.d(TAG, "Lid switch state: " + (lidOpen ? "open" : "closed"));
422         }
423         notifyDeviceStateChangedIfNeeded();
424     }
425 
426     @Override
onSensorChanged(SensorEvent event)427     public void onSensorChanged(SensorEvent event) {
428         synchronized (mLock) {
429             mLatestSensorEvent.put(event.sensor, event);
430         }
431         notifyDeviceStateChangedIfNeeded();
432     }
433 
434     @Override
onAccuracyChanged(Sensor sensor, int accuracy)435     public void onAccuracyChanged(Sensor sensor, int accuracy) {
436         // Do nothing.
437     }
438 
439     /**
440      * Implementation of {@link BooleanSupplier} that returns {@code true} if the expected lid
441      * switch open state matches {@link #mIsLidOpen}.
442      */
443     private final class LidSwitchBooleanSupplier implements BooleanSupplier {
444         private final boolean mExpectedOpen;
445 
LidSwitchBooleanSupplier(boolean expectedOpen)446         LidSwitchBooleanSupplier(boolean expectedOpen) {
447             mExpectedOpen = expectedOpen;
448         }
449 
450         @Override
getAsBoolean()451         public boolean getAsBoolean() {
452             synchronized (mLock) {
453                 if (mIsLidOpen == null) {
454                     throw new IllegalStateException("Have not received lid switch value.");
455                 }
456 
457                 return mIsLidOpen == mExpectedOpen;
458             }
459         }
460     }
461 
462     /**
463      * Implementation of {@link BooleanSupplier} that returns {@code true} if the latest
464      * {@link SensorEvent#values sensor event values} for the specified {@link Sensor} adhere to
465      * the supplied {@link NumericRange ranges}.
466      */
467     private final class SensorBooleanSupplier implements BooleanSupplier {
468         @NonNull
469         private final Sensor mSensor;
470         @NonNull
471         private final List<NumericRange> mExpectedValues;
472 
SensorBooleanSupplier(@onNull Sensor sensor, @NonNull List<NumericRange> expectedValues)473         SensorBooleanSupplier(@NonNull Sensor sensor, @NonNull List<NumericRange> expectedValues) {
474             mSensor = sensor;
475             mExpectedValues = expectedValues;
476         }
477 
478         @Override
getAsBoolean()479         public boolean getAsBoolean() {
480             synchronized (mLock) {
481                 SensorEvent latestEvent = mLatestSensorEvent.get(mSensor);
482                 if (latestEvent == null) {
483                     throw new IllegalStateException("Have not received sensor event.");
484                 }
485 
486                 if (latestEvent.values.length < mExpectedValues.size()) {
487                     throw new RuntimeException("Number of supplied numeric range(s) does not "
488                             + "match the number of values in the latest sensor event for sensor: "
489                             + mSensor);
490                 }
491 
492                 for (int i = 0; i < mExpectedValues.size(); i++) {
493                     if (!adheresToRange(latestEvent.values[i], mExpectedValues.get(i))) {
494                         return false;
495                     }
496                 }
497                 return true;
498             }
499         }
500 
501         /**
502          * Returns {@code true} if the supplied {@code value} adheres to the constraints specified
503          * in {@code range}.
504          */
adheresToRange(float value, @NonNull NumericRange range)505         private boolean adheresToRange(float value, @NonNull NumericRange range) {
506             final BigDecimal min = range.getMin_optional();
507             if (min != null) {
508                 if (DEBUG) {
509                     Slog.d(TAG, "value: " + value + ", constraint min: " + min.floatValue());
510                 }
511                 if (value <= min.floatValue()) {
512                     return false;
513                 }
514             }
515 
516             final BigDecimal minInclusive = range.getMinInclusive_optional();
517             if (minInclusive != null) {
518                 if (DEBUG) {
519                     Slog.d(TAG, "value: " + value + ", constraint min-inclusive: "
520                             + minInclusive.floatValue());
521                 }
522                 if (value < minInclusive.floatValue()) {
523                     return false;
524                 }
525             }
526 
527             final BigDecimal max = range.getMax_optional();
528             if (max != null) {
529                 if (DEBUG) {
530                     Slog.d(TAG, "value: " + value + ", constraint max: " + max.floatValue());
531                 }
532                 if (value >= max.floatValue()) {
533                     return false;
534                 }
535             }
536 
537             final BigDecimal maxInclusive = range.getMaxInclusive_optional();
538             if (maxInclusive != null) {
539                 if (DEBUG) {
540                     Slog.d(TAG, "value: " + value + ", constraint max-inclusive: "
541                             + maxInclusive.floatValue());
542                 }
543                 if (value > maxInclusive.floatValue()) {
544                     return false;
545                 }
546             }
547 
548             return true;
549         }
550     }
551 
552     /**
553      * Implementation of {@link BooleanSupplier} whose result is the product of an AND operation
554      * applied to the result of all child suppliers.
555      */
556     private static final class AndBooleanSupplier implements BooleanSupplier {
557         @NonNull
558         List<BooleanSupplier> mBooleanSuppliers;
559 
AndBooleanSupplier(@onNull List<BooleanSupplier> booleanSuppliers)560         AndBooleanSupplier(@NonNull List<BooleanSupplier> booleanSuppliers) {
561             mBooleanSuppliers = booleanSuppliers;
562         }
563 
564         @Override
getAsBoolean()565         public boolean getAsBoolean() {
566             for (int i = 0; i < mBooleanSuppliers.size(); i++) {
567                 if (!mBooleanSuppliers.get(i).getAsBoolean()) {
568                     return false;
569                 }
570             }
571             return true;
572         }
573     }
574 
575     /**
576      * Returns the device state configuration file that should be used, or {@code null} if no file
577      * is present on the device.
578      * <p>
579      * Defaults to returning a config file present in the data/ dir at
580      * {@link #DATA_CONFIG_FILE_PATH}, and then falls back to the config file in the vendor/ dir
581      * at {@link #VENDOR_CONFIG_FILE_PATH} if no config file is found in the data/ dir.
582      */
583     @Nullable
getConfigurationFile()584     private static File getConfigurationFile() {
585         final File configFileFromDataDir = Environment.buildPath(Environment.getDataDirectory(),
586                 DATA_CONFIG_FILE_PATH, CONFIG_FILE_NAME);
587         if (configFileFromDataDir.exists()) {
588             return configFileFromDataDir;
589         }
590 
591         final File configFileFromVendorDir = Environment.buildPath(Environment.getVendorDirectory(),
592                 VENDOR_CONFIG_FILE_PATH, CONFIG_FILE_NAME);
593         if (configFileFromVendorDir.exists()) {
594             return configFileFromVendorDir;
595         }
596 
597         return null;
598     }
599 
600     @GuardedBy("mLock")
dumpSensorValues()601     private void dumpSensorValues() {
602         Slog.i(TAG, "Sensor values:");
603         for (Sensor sensor : mLatestSensorEvent.keySet()) {
604             SensorEvent sensorEvent = mLatestSensorEvent.get(sensor);
605             if (sensorEvent != null) {
606                 Slog.i(TAG, sensor.getName() + ": " + Arrays.toString(sensorEvent.values));
607             } else {
608                 Slog.i(TAG, sensor.getName() + ": null");
609             }
610         }
611     }
612 
613     /**
614      * Tries to parse the provided file into a {@link DeviceStateConfig} object. Returns
615      * {@code null} if the file could not be successfully parsed.
616      */
617     @Nullable
parseConfig(@onNull ReadableConfig readableConfig)618     private static DeviceStateConfig parseConfig(@NonNull ReadableConfig readableConfig) {
619         try (InputStream in = readableConfig.openRead();
620                 InputStream bin = new BufferedInputStream(in)) {
621             return XmlParser.read(bin);
622         } catch (IOException | DatatypeConfigurationException | XmlPullParserException e) {
623             Slog.e(TAG, "Encountered an error while reading device state config", e);
624         }
625         return null;
626     }
627 
628     /** Implementation of {@link ReadableConfig} that reads config data from a file. */
629     private static final class ReadableFileConfig implements ReadableConfig {
630         @NonNull
631         private final File mFile;
632 
ReadableFileConfig(@onNull File file)633         private ReadableFileConfig(@NonNull File file) {
634             mFile = file;
635         }
636 
637         @Override
openRead()638         public InputStream openRead() throws IOException {
639             return new FileInputStream(mFile);
640         }
641     }
642 }
643