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