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