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 98 /** Interface that allows reading the device state configuration. */ 99 interface ReadableConfig { 100 @NonNull openRead()101 InputStream openRead() throws IOException; 102 } 103 104 /** 105 * Returns a new {@link DeviceStateProviderImpl} instance. 106 * 107 * @param context the {@link Context} that should be used to access system services. 108 */ create(@onNull Context context)109 public static DeviceStateProviderImpl create(@NonNull Context context) { 110 File configFile = getConfigurationFile(); 111 if (configFile == null) { 112 return createFromConfig(context, null); 113 } 114 return createFromConfig(context, new ReadableFileConfig(configFile)); 115 } 116 117 /** 118 * Returns a new {@link DeviceStateProviderImpl} instance. 119 * 120 * @param context the {@link Context} that should be used to access system services. 121 * @param readableConfig the config the provider instance should read supported states from. 122 */ 123 @VisibleForTesting createFromConfig(@onNull Context context, @Nullable ReadableConfig readableConfig)124 static DeviceStateProviderImpl createFromConfig(@NonNull Context context, 125 @Nullable ReadableConfig readableConfig) { 126 List<DeviceState> deviceStateList = new ArrayList<>(); 127 List<Conditions> conditionsList = new ArrayList<>(); 128 129 if (readableConfig != null) { 130 DeviceStateConfig config = parseConfig(readableConfig); 131 if (config != null) { 132 for (com.android.server.policy.devicestate.config.DeviceState stateConfig : 133 config.getDeviceState()) { 134 final int state = stateConfig.getIdentifier().intValue(); 135 final String name = stateConfig.getName() == null ? "" : stateConfig.getName(); 136 137 int flags = 0; 138 final Flags configFlags = stateConfig.getFlags(); 139 if (configFlags != null) { 140 List<String> configFlagStrings = configFlags.getFlag(); 141 for (int i = 0; i < configFlagStrings.size(); i++) { 142 final String configFlagString = configFlagStrings.get(i); 143 switch (configFlagString) { 144 case "FLAG_CANCEL_STICKY_REQUESTS": 145 flags |= DeviceState.FLAG_CANCEL_STICKY_REQUESTS; 146 break; 147 default: 148 Slog.w(TAG, "Parsed unknown flag with name: " 149 + configFlagString); 150 break; 151 } 152 } 153 } 154 155 deviceStateList.add(new DeviceState(state, name, flags)); 156 157 final Conditions condition = stateConfig.getConditions(); 158 conditionsList.add(condition); 159 } 160 } 161 } 162 163 if (deviceStateList.size() == 0) { 164 deviceStateList.add(DEFAULT_DEVICE_STATE); 165 conditionsList.add(null); 166 } 167 return new DeviceStateProviderImpl(context, deviceStateList, conditionsList); 168 } 169 170 // Lock for internal state. 171 private final Object mLock = new Object(); 172 private final Context mContext; 173 // List of supported states in ascending order based on their identifier. 174 private final DeviceState[] mOrderedStates; 175 // Map of state identifier to a boolean supplier that returns true when all required conditions 176 // are met for the device to be in the state. 177 private final SparseArray<BooleanSupplier> mStateConditions = new SparseArray<>(); 178 179 @Nullable 180 @GuardedBy("mLock") 181 private Listener mListener = null; 182 @GuardedBy("mLock") 183 private int mLastReportedState = INVALID_DEVICE_STATE; 184 185 @GuardedBy("mLock") 186 private Boolean mIsLidOpen; 187 @GuardedBy("mLock") 188 private final Map<Sensor, SensorEvent> mLatestSensorEvent = new ArrayMap<>(); 189 DeviceStateProviderImpl(@onNull Context context, @NonNull List<DeviceState> deviceStates, @NonNull List<Conditions> stateConditions)190 private DeviceStateProviderImpl(@NonNull Context context, 191 @NonNull List<DeviceState> deviceStates, 192 @NonNull List<Conditions> stateConditions) { 193 Preconditions.checkArgument(deviceStates.size() == stateConditions.size(), 194 "Number of device states must be equal to the number of device state conditions."); 195 196 mContext = context; 197 198 DeviceState[] orderedStates = deviceStates.toArray(new DeviceState[deviceStates.size()]); 199 Arrays.sort(orderedStates, Comparator.comparingInt(DeviceState::getIdentifier)); 200 mOrderedStates = orderedStates; 201 202 setStateConditions(deviceStates, stateConditions); 203 } 204 setStateConditions(@onNull List<DeviceState> deviceStates, @NonNull List<Conditions> stateConditions)205 private void setStateConditions(@NonNull List<DeviceState> deviceStates, 206 @NonNull List<Conditions> stateConditions) { 207 // Whether or not this instance should register to receive lid switch notifications from 208 // InputManagerInternal. If there are no device state conditions that are based on the lid 209 // switch there is no need to register for a callback. 210 boolean shouldListenToLidSwitch = false; 211 212 // The set of Sensor(s) that this instance should register to receive SensorEvent(s) from. 213 final ArraySet<Sensor> sensorsToListenTo = new ArraySet<>(); 214 215 for (int i = 0; i < stateConditions.size(); i++) { 216 final int state = deviceStates.get(i).getIdentifier(); 217 if (DEBUG) { 218 Slog.d(TAG, "Evaluating conditions for device state " + state 219 + " (" + deviceStates.get(i).getName() + ")"); 220 } 221 final Conditions conditions = stateConditions.get(i); 222 if (conditions == null) { 223 mStateConditions.put(state, TRUE_BOOLEAN_SUPPLIER); 224 continue; 225 } 226 227 // Whether or not all the required hardware components could be found that match the 228 // requirements from the config. 229 boolean allRequiredComponentsFound = true; 230 // Whether or not this condition requires the lid switch. 231 boolean lidSwitchRequired = false; 232 // Set of sensors required for this condition. 233 ArraySet<Sensor> sensorsRequired = new ArraySet<>(); 234 235 List<BooleanSupplier> suppliers = new ArrayList<>(); 236 237 LidSwitchCondition lidSwitchCondition = conditions.getLidSwitch(); 238 if (lidSwitchCondition != null) { 239 suppliers.add(new LidSwitchBooleanSupplier(lidSwitchCondition.getOpen())); 240 lidSwitchRequired = true; 241 if (DEBUG) { 242 Slog.d(TAG, "Lid switch required"); 243 } 244 } 245 246 List<SensorCondition> sensorConditions = conditions.getSensor(); 247 for (int j = 0; j < sensorConditions.size(); j++) { 248 SensorCondition sensorCondition = sensorConditions.get(j); 249 final String expectedSensorType = sensorCondition.getType(); 250 final String expectedSensorName = sensorCondition.getName(); 251 252 final Sensor foundSensor = findSensor(expectedSensorType, expectedSensorName); 253 if (foundSensor == null) { 254 Slog.e(TAG, "Failed to find Sensor with type: " + expectedSensorType 255 + " and name: " + expectedSensorName); 256 allRequiredComponentsFound = false; 257 break; 258 } 259 260 if (DEBUG) { 261 Slog.d(TAG, "Found sensor with type: " + expectedSensorType 262 + " (" + expectedSensorName + ")"); 263 } 264 265 suppliers.add(new SensorBooleanSupplier(foundSensor, sensorCondition.getValue())); 266 sensorsRequired.add(foundSensor); 267 } 268 269 if (allRequiredComponentsFound) { 270 shouldListenToLidSwitch |= lidSwitchRequired; 271 sensorsToListenTo.addAll(sensorsRequired); 272 273 if (suppliers.size() > 1) { 274 mStateConditions.put(state, new AndBooleanSupplier(suppliers)); 275 } else if (suppliers.size() > 0) { 276 // No need to wrap with an AND supplier if there is only 1. 277 mStateConditions.put(state, suppliers.get(0)); 278 } else { 279 // There are no conditions for this state. Default to always true. 280 mStateConditions.put(state, TRUE_BOOLEAN_SUPPLIER); 281 } 282 } else { 283 // Failed to setup this condition. This can happen if a sensor is missing. Default 284 // this state to always false. 285 mStateConditions.put(state, FALSE_BOOLEAN_SUPPLIER); 286 } 287 } 288 289 if (shouldListenToLidSwitch) { 290 InputManagerInternal inputManager = LocalServices.getService( 291 InputManagerInternal.class); 292 inputManager.registerLidSwitchCallback(this); 293 } 294 295 final SensorManager sensorManager = mContext.getSystemService(SensorManager.class); 296 for (int i = 0; i < sensorsToListenTo.size(); i++) { 297 Sensor sensor = sensorsToListenTo.valueAt(i); 298 sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_FASTEST); 299 } 300 } 301 302 @Nullable findSensor(String type, String name)303 private Sensor findSensor(String type, String name) { 304 final SensorManager sensorManager = mContext.getSystemService(SensorManager.class); 305 final List<Sensor> sensors = sensorManager.getSensorList(Sensor.TYPE_ALL); 306 for (int sensorIndex = 0; sensorIndex < sensors.size(); sensorIndex++) { 307 final Sensor sensor = sensors.get(sensorIndex); 308 final String sensorType = sensor.getStringType(); 309 final String sensorName = sensor.getName(); 310 311 if (sensorType == null || sensorName == null) { 312 continue; 313 } 314 315 if (sensorType.equals(type) && sensorName.equals(name)) { 316 return sensor; 317 } 318 } 319 return null; 320 } 321 322 @Override setListener(Listener listener)323 public void setListener(Listener listener) { 324 synchronized (mLock) { 325 if (mListener != null) { 326 throw new RuntimeException("Provider already has a listener set."); 327 } 328 mListener = listener; 329 } 330 notifySupportedStatesChanged(); 331 notifyDeviceStateChangedIfNeeded(); 332 } 333 334 /** Notifies the listener that the set of supported device states has changed. */ notifySupportedStatesChanged()335 private void notifySupportedStatesChanged() { 336 DeviceState[] supportedStates; 337 synchronized (mLock) { 338 if (mListener == null) { 339 return; 340 } 341 342 supportedStates = Arrays.copyOf(mOrderedStates, mOrderedStates.length); 343 } 344 345 mListener.onSupportedDeviceStatesChanged(supportedStates); 346 } 347 348 /** Computes the current device state and notifies the listener of a change, if needed. */ notifyDeviceStateChangedIfNeeded()349 void notifyDeviceStateChangedIfNeeded() { 350 int stateToReport = INVALID_DEVICE_STATE; 351 synchronized (mLock) { 352 if (mListener == null) { 353 return; 354 } 355 356 int newState = mOrderedStates[0].getIdentifier(); 357 for (int i = 0; i < mOrderedStates.length; i++) { 358 int state = mOrderedStates[i].getIdentifier(); 359 if (DEBUG) { 360 Slog.d(TAG, "Checking conditions for " + mOrderedStates[i].getName() + "(" 361 + i + ")"); 362 } 363 boolean conditionSatisfied; 364 try { 365 conditionSatisfied = mStateConditions.get(state).getAsBoolean(); 366 } catch (IllegalStateException e) { 367 // Failed to compute the current state based on current available data. Return 368 // with the expectation that notifyDeviceStateChangedIfNeeded() will be called 369 // when a callback with the missing data is triggered. 370 if (DEBUG) { 371 Slog.d(TAG, "Unable to check current state", e); 372 } 373 return; 374 } 375 376 if (conditionSatisfied) { 377 if (DEBUG) { 378 Slog.d(TAG, "Device State conditions satisfied, transition to " + state); 379 } 380 newState = state; 381 break; 382 } 383 } 384 385 if (newState != mLastReportedState) { 386 mLastReportedState = newState; 387 stateToReport = newState; 388 } 389 } 390 391 if (stateToReport != INVALID_DEVICE_STATE) { 392 mListener.onStateChanged(stateToReport); 393 } 394 } 395 396 @Override notifyLidSwitchChanged(long whenNanos, boolean lidOpen)397 public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen) { 398 synchronized (mLock) { 399 mIsLidOpen = lidOpen; 400 } 401 if (DEBUG) { 402 Slog.d(TAG, "Lid switch state: " + (lidOpen ? "open" : "closed")); 403 } 404 notifyDeviceStateChangedIfNeeded(); 405 } 406 407 @Override onSensorChanged(SensorEvent event)408 public void onSensorChanged(SensorEvent event) { 409 synchronized (mLock) { 410 mLatestSensorEvent.put(event.sensor, event); 411 } 412 notifyDeviceStateChangedIfNeeded(); 413 } 414 415 @Override onAccuracyChanged(Sensor sensor, int accuracy)416 public void onAccuracyChanged(Sensor sensor, int accuracy) { 417 // Do nothing. 418 } 419 420 /** 421 * Implementation of {@link BooleanSupplier} that returns {@code true} if the expected lid 422 * switch open state matches {@link #mIsLidOpen}. 423 */ 424 private final class LidSwitchBooleanSupplier implements BooleanSupplier { 425 private final boolean mExpectedOpen; 426 LidSwitchBooleanSupplier(boolean expectedOpen)427 LidSwitchBooleanSupplier(boolean expectedOpen) { 428 mExpectedOpen = expectedOpen; 429 } 430 431 @Override getAsBoolean()432 public boolean getAsBoolean() { 433 synchronized (mLock) { 434 if (mIsLidOpen == null) { 435 throw new IllegalStateException("Have not received lid switch value."); 436 } 437 438 return mIsLidOpen == mExpectedOpen; 439 } 440 } 441 } 442 443 /** 444 * Implementation of {@link BooleanSupplier} that returns {@code true} if the latest 445 * {@link SensorEvent#values sensor event values} for the specified {@link Sensor} adhere to 446 * the supplied {@link NumericRange ranges}. 447 */ 448 private final class SensorBooleanSupplier implements BooleanSupplier { 449 @NonNull 450 private final Sensor mSensor; 451 @NonNull 452 private final List<NumericRange> mExpectedValues; 453 SensorBooleanSupplier(@onNull Sensor sensor, @NonNull List<NumericRange> expectedValues)454 SensorBooleanSupplier(@NonNull Sensor sensor, @NonNull List<NumericRange> expectedValues) { 455 mSensor = sensor; 456 mExpectedValues = expectedValues; 457 } 458 459 @Override getAsBoolean()460 public boolean getAsBoolean() { 461 synchronized (mLock) { 462 SensorEvent latestEvent = mLatestSensorEvent.get(mSensor); 463 if (latestEvent == null) { 464 throw new IllegalStateException("Have not received sensor event."); 465 } 466 467 if (latestEvent.values.length < mExpectedValues.size()) { 468 throw new RuntimeException("Number of supplied numeric range(s) does not " 469 + "match the number of values in the latest sensor event for sensor: " 470 + mSensor); 471 } 472 473 for (int i = 0; i < mExpectedValues.size(); i++) { 474 if (!adheresToRange(latestEvent.values[i], mExpectedValues.get(i))) { 475 return false; 476 } 477 } 478 return true; 479 } 480 } 481 482 /** 483 * Returns {@code true} if the supplied {@code value} adheres to the constraints specified 484 * in {@code range}. 485 */ adheresToRange(float value, @NonNull NumericRange range)486 private boolean adheresToRange(float value, @NonNull NumericRange range) { 487 final BigDecimal min = range.getMin_optional(); 488 if (min != null) { 489 if (DEBUG) { 490 Slog.d(TAG, "value: " + value + ", constraint min: " + min.floatValue()); 491 } 492 if (value <= min.floatValue()) { 493 return false; 494 } 495 } 496 497 final BigDecimal minInclusive = range.getMinInclusive_optional(); 498 if (minInclusive != null) { 499 if (DEBUG) { 500 Slog.d(TAG, "value: " + value + ", constraint min-inclusive: " 501 + minInclusive.floatValue()); 502 } 503 if (value < minInclusive.floatValue()) { 504 return false; 505 } 506 } 507 508 final BigDecimal max = range.getMax_optional(); 509 if (max != null) { 510 if (DEBUG) { 511 Slog.d(TAG, "value: " + value + ", constraint max: " + max.floatValue()); 512 } 513 if (value >= max.floatValue()) { 514 return false; 515 } 516 } 517 518 final BigDecimal maxInclusive = range.getMaxInclusive_optional(); 519 if (maxInclusive != null) { 520 if (DEBUG) { 521 Slog.d(TAG, "value: " + value + ", constraint max-inclusive: " 522 + maxInclusive.floatValue()); 523 } 524 if (value > maxInclusive.floatValue()) { 525 return false; 526 } 527 } 528 529 return true; 530 } 531 } 532 533 /** 534 * Implementation of {@link BooleanSupplier} whose result is the product of an AND operation 535 * applied to the result of all child suppliers. 536 */ 537 private static final class AndBooleanSupplier implements BooleanSupplier { 538 @NonNull 539 List<BooleanSupplier> mBooleanSuppliers; 540 AndBooleanSupplier(@onNull List<BooleanSupplier> booleanSuppliers)541 AndBooleanSupplier(@NonNull List<BooleanSupplier> booleanSuppliers) { 542 mBooleanSuppliers = booleanSuppliers; 543 } 544 545 @Override getAsBoolean()546 public boolean getAsBoolean() { 547 for (int i = 0; i < mBooleanSuppliers.size(); i++) { 548 if (!mBooleanSuppliers.get(i).getAsBoolean()) { 549 return false; 550 } 551 } 552 return true; 553 } 554 } 555 556 /** 557 * Returns the device state configuration file that should be used, or {@code null} if no file 558 * is present on the device. 559 * <p> 560 * Defaults to returning a config file present in the data/ dir at 561 * {@link #DATA_CONFIG_FILE_PATH}, and then falls back to the config file in the vendor/ dir 562 * at {@link #VENDOR_CONFIG_FILE_PATH} if no config file is found in the data/ dir. 563 */ 564 @Nullable getConfigurationFile()565 private static File getConfigurationFile() { 566 final File configFileFromDataDir = Environment.buildPath(Environment.getDataDirectory(), 567 DATA_CONFIG_FILE_PATH, CONFIG_FILE_NAME); 568 if (configFileFromDataDir.exists()) { 569 return configFileFromDataDir; 570 } 571 572 final File configFileFromVendorDir = Environment.buildPath(Environment.getVendorDirectory(), 573 VENDOR_CONFIG_FILE_PATH, CONFIG_FILE_NAME); 574 if (configFileFromVendorDir.exists()) { 575 return configFileFromVendorDir; 576 } 577 578 return null; 579 } 580 581 /** 582 * Tries to parse the provided file into a {@link DeviceStateConfig} object. Returns 583 * {@code null} if the file could not be successfully parsed. 584 */ 585 @Nullable parseConfig(@onNull ReadableConfig readableConfig)586 private static DeviceStateConfig parseConfig(@NonNull ReadableConfig readableConfig) { 587 try (InputStream in = readableConfig.openRead(); 588 InputStream bin = new BufferedInputStream(in)) { 589 return XmlParser.read(bin); 590 } catch (IOException | DatatypeConfigurationException | XmlPullParserException e) { 591 Slog.e(TAG, "Encountered an error while reading device state config", e); 592 } 593 return null; 594 } 595 596 /** Implementation of {@link ReadableConfig} that reads config data from a file. */ 597 private static final class ReadableFileConfig implements ReadableConfig { 598 @NonNull 599 private final File mFile; 600 ReadableFileConfig(@onNull File file)601 private ReadableFileConfig(@NonNull File file) { 602 mFile = file; 603 } 604 605 @Override openRead()606 public InputStream openRead() throws IOException { 607 return new FileInputStream(mFile); 608 } 609 } 610 } 611