blob: 3644054e3b780c1110e2e8f9db216aab2e783f85 [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.policy;
import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Environment;
import android.os.PowerManager;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
import com.android.server.LocalServices;
import com.android.server.devicestate.DeviceState;
import com.android.server.devicestate.DeviceStateProvider;
import com.android.server.input.InputManagerInternal;
import com.android.server.policy.devicestate.config.Conditions;
import com.android.server.policy.devicestate.config.DeviceStateConfig;
import com.android.server.policy.devicestate.config.Flags;
import com.android.server.policy.devicestate.config.LidSwitchCondition;
import com.android.server.policy.devicestate.config.NumericRange;
import com.android.server.policy.devicestate.config.SensorCondition;
import com.android.server.policy.devicestate.config.XmlParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.function.BooleanSupplier;
import javax.xml.datatype.DatatypeConfigurationException;
/**
* Implementation of {@link DeviceStateProvider} that reads the set of supported device states
* from a configuration file provided at either /vendor/etc/devicestate or
* /data/system/devicestate/.
* <p>
* When a device state configuration file is present this provider will consider the provided
* {@link Conditions} block for each declared state, halting and returning when the first set of
* conditions for a device state match the current system state. If there are multiple states whose
* conditions match the current system state the matching state with the smallest integer identifier
* will be returned. When no declared state matches the current system state, the device state with
* the smallest integer identifier will be returned.
* <p>
* By default, the provider reports {@link #DEFAULT_DEVICE_STATE} when no configuration file is
* provided.
*/
public final class DeviceStateProviderImpl implements DeviceStateProvider,
InputManagerInternal.LidSwitchCallback, SensorEventListener,
PowerManager.OnThermalStatusChangedListener {
private static final String TAG = "DeviceStateProviderImpl";
private static final boolean DEBUG = false;
private static final BooleanSupplier TRUE_BOOLEAN_SUPPLIER = () -> true;
private static final BooleanSupplier FALSE_BOOLEAN_SUPPLIER = () -> false;
@VisibleForTesting
static final DeviceState DEFAULT_DEVICE_STATE = new DeviceState(MINIMUM_DEVICE_STATE,
"DEFAULT", 0 /* flags */);
private static final String VENDOR_CONFIG_FILE_PATH = "etc/devicestate/";
private static final String DATA_CONFIG_FILE_PATH = "system/devicestate/";
private static final String CONFIG_FILE_NAME = "device_state_configuration.xml";
private static final String FLAG_CANCEL_OVERRIDE_REQUESTS = "FLAG_CANCEL_OVERRIDE_REQUESTS";
private static final String FLAG_APP_INACCESSIBLE = "FLAG_APP_INACCESSIBLE";
private static final String FLAG_EMULATED_ONLY = "FLAG_EMULATED_ONLY";
private static final String FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP =
"FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP";
private static final String FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL =
"FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL";
private static final String FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE =
"FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE";
/** Interface that allows reading the device state configuration. */
interface ReadableConfig {
@NonNull
InputStream openRead() throws IOException;
}
/**
* Returns a new {@link DeviceStateProviderImpl} instance.
*
* @param context the {@link Context} that should be used to access system services.
*/
public static DeviceStateProviderImpl create(@NonNull Context context) {
File configFile = getConfigurationFile();
if (configFile == null) {
return createFromConfig(context, null);
}
return createFromConfig(context, new ReadableFileConfig(configFile));
}
/**
* Returns a new {@link DeviceStateProviderImpl} instance.
*
* @param context the {@link Context} that should be used to access system services.
* @param readableConfig the config the provider instance should read supported states from.
*/
@VisibleForTesting
static DeviceStateProviderImpl createFromConfig(@NonNull Context context,
@Nullable ReadableConfig readableConfig) {
List<DeviceState> deviceStateList = new ArrayList<>();
List<Conditions> conditionsList = new ArrayList<>();
if (readableConfig != null) {
DeviceStateConfig config = parseConfig(readableConfig);
if (config != null) {
for (com.android.server.policy.devicestate.config.DeviceState stateConfig :
config.getDeviceState()) {
final int state = stateConfig.getIdentifier().intValue();
final String name = stateConfig.getName() == null ? "" : stateConfig.getName();
int flags = 0;
final Flags configFlags = stateConfig.getFlags();
if (configFlags != null) {
List<String> configFlagStrings = configFlags.getFlag();
for (int i = 0; i < configFlagStrings.size(); i++) {
final String configFlagString = configFlagStrings.get(i);
switch (configFlagString) {
case FLAG_CANCEL_OVERRIDE_REQUESTS:
flags |= DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS;
break;
case FLAG_APP_INACCESSIBLE:
flags |= DeviceState.FLAG_APP_INACCESSIBLE;
break;
case FLAG_EMULATED_ONLY:
flags |= DeviceState.FLAG_EMULATED_ONLY;
break;
case FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP:
flags |= DeviceState.FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP;
break;
case FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL:
flags |= DeviceState
.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL;
break;
case FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE:
flags |= DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE;
default:
Slog.w(TAG, "Parsed unknown flag with name: "
+ configFlagString);
break;
}
}
}
deviceStateList.add(new DeviceState(state, name, flags));
final Conditions condition = stateConfig.getConditions();
conditionsList.add(condition);
}
}
}
if (deviceStateList.size() == 0) {
deviceStateList.add(DEFAULT_DEVICE_STATE);
conditionsList.add(null);
}
return new DeviceStateProviderImpl(context, deviceStateList, conditionsList);
}
// Lock for internal state.
private final Object mLock = new Object();
private final Context mContext;
// List of supported states in ascending order based on their identifier.
private final DeviceState[] mOrderedStates;
// Map of state identifier to a boolean supplier that returns true when all required conditions
// are met for the device to be in the state.
private final SparseArray<BooleanSupplier> mStateConditions = new SparseArray<>();
@Nullable
@GuardedBy("mLock")
private Listener mListener = null;
@GuardedBy("mLock")
private int mLastReportedState = INVALID_DEVICE_STATE;
@GuardedBy("mLock")
private Boolean mIsLidOpen;
@GuardedBy("mLock")
private final Map<Sensor, SensorEvent> mLatestSensorEvent = new ArrayMap<>();
@GuardedBy("mLock")
private @PowerManager.ThermalStatus int mThermalStatus = PowerManager.THERMAL_STATUS_NONE;
@GuardedBy("mLock")
private boolean mPowerSaveModeEnabled;
private DeviceStateProviderImpl(@NonNull Context context,
@NonNull List<DeviceState> deviceStates,
@NonNull List<Conditions> stateConditions) {
Preconditions.checkArgument(deviceStates.size() == stateConditions.size(),
"Number of device states must be equal to the number of device state conditions.");
mContext = context;
DeviceState[] orderedStates = deviceStates.toArray(new DeviceState[deviceStates.size()]);
Arrays.sort(orderedStates, Comparator.comparingInt(DeviceState::getIdentifier));
mOrderedStates = orderedStates;
setStateConditions(deviceStates, stateConditions);
PowerManager powerManager = context.getSystemService(PowerManager.class);
if (powerManager != null) {
// If any of the device states are thermal sensitive, i.e. it should be disabled when
// the device is overheating, then we will update the list of supported states when
// thermal status changes.
if (hasThermalSensitiveState(deviceStates)) {
powerManager.addThermalStatusListener(this);
}
// If any of the device states are power sensitive, i.e. it should be disabled when
// power save mode is enabled, then we will update the list of supported states when
// power save mode is toggled.
if (hasPowerSaveSensitiveState(deviceStates)) {
IntentFilter filter = new IntentFilter(
PowerManager.ACTION_POWER_SAVE_MODE_CHANGED_INTERNAL);
BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (PowerManager.ACTION_POWER_SAVE_MODE_CHANGED_INTERNAL.equals(
intent.getAction())) {
onPowerSaveModeChanged(powerManager.isPowerSaveMode());
}
}
};
mContext.registerReceiver(receiver, filter);
}
}
}
private void setStateConditions(@NonNull List<DeviceState> deviceStates,
@NonNull List<Conditions> stateConditions) {
// Whether or not this instance should register to receive lid switch notifications from
// InputManagerInternal. If there are no device state conditions that are based on the lid
// switch there is no need to register for a callback.
boolean shouldListenToLidSwitch = false;
// The set of Sensor(s) that this instance should register to receive SensorEvent(s) from.
final ArraySet<Sensor> sensorsToListenTo = new ArraySet<>();
for (int i = 0; i < stateConditions.size(); i++) {
final int state = deviceStates.get(i).getIdentifier();
if (DEBUG) {
Slog.d(TAG, "Evaluating conditions for device state " + state
+ " (" + deviceStates.get(i).getName() + ")");
}
final Conditions conditions = stateConditions.get(i);
if (conditions == null) {
// If this state has the FLAG_EMULATED_ONLY flag on it, it should never be triggered
// by a physical hardware change, and should always return false for it's conditions
if (deviceStates.get(i).hasFlag(DeviceState.FLAG_EMULATED_ONLY)) {
mStateConditions.put(state, FALSE_BOOLEAN_SUPPLIER);
} else {
mStateConditions.put(state, TRUE_BOOLEAN_SUPPLIER);
}
continue;
}
// Whether or not all the required hardware components could be found that match the
// requirements from the config.
boolean allRequiredComponentsFound = true;
// Whether or not this condition requires the lid switch.
boolean lidSwitchRequired = false;
// Set of sensors required for this condition.
ArraySet<Sensor> sensorsRequired = new ArraySet<>();
List<BooleanSupplier> suppliers = new ArrayList<>();
LidSwitchCondition lidSwitchCondition = conditions.getLidSwitch();
if (lidSwitchCondition != null) {
suppliers.add(new LidSwitchBooleanSupplier(lidSwitchCondition.getOpen()));
lidSwitchRequired = true;
if (DEBUG) {
Slog.d(TAG, "Lid switch required");
}
}
List<SensorCondition> sensorConditions = conditions.getSensor();
for (int j = 0; j < sensorConditions.size(); j++) {
SensorCondition sensorCondition = sensorConditions.get(j);
final String expectedSensorType = sensorCondition.getType();
final String expectedSensorName = sensorCondition.getName();
final Sensor foundSensor = findSensor(expectedSensorType, expectedSensorName);
if (foundSensor == null) {
Slog.e(TAG, "Failed to find Sensor with type: " + expectedSensorType
+ " and name: " + expectedSensorName);
allRequiredComponentsFound = false;
break;
}
if (DEBUG) {
Slog.d(TAG, "Found sensor with type: " + expectedSensorType
+ " (" + expectedSensorName + ")");
}
suppliers.add(new SensorBooleanSupplier(foundSensor, sensorCondition.getValue()));
sensorsRequired.add(foundSensor);
}
if (allRequiredComponentsFound) {
shouldListenToLidSwitch |= lidSwitchRequired;
sensorsToListenTo.addAll(sensorsRequired);
if (suppliers.size() > 1) {
mStateConditions.put(state, new AndBooleanSupplier(suppliers));
} else if (suppliers.size() > 0) {
// No need to wrap with an AND supplier if there is only 1.
mStateConditions.put(state, suppliers.get(0));
} else {
// There are no conditions for this state. Default to always true.
mStateConditions.put(state, TRUE_BOOLEAN_SUPPLIER);
}
} else {
// Failed to setup this condition. This can happen if a sensor is missing. Default
// this state to always false.
mStateConditions.put(state, FALSE_BOOLEAN_SUPPLIER);
}
}
if (shouldListenToLidSwitch) {
InputManagerInternal inputManager = LocalServices.getService(
InputManagerInternal.class);
inputManager.registerLidSwitchCallback(this);
}
final SensorManager sensorManager = mContext.getSystemService(SensorManager.class);
for (int i = 0; i < sensorsToListenTo.size(); i++) {
Sensor sensor = sensorsToListenTo.valueAt(i);
sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_FASTEST);
}
}
@Nullable
private Sensor findSensor(String type, String name) {
final SensorManager sensorManager = mContext.getSystemService(SensorManager.class);
final List<Sensor> sensors = sensorManager.getSensorList(Sensor.TYPE_ALL);
for (int sensorIndex = 0; sensorIndex < sensors.size(); sensorIndex++) {
final Sensor sensor = sensors.get(sensorIndex);
final String sensorType = sensor.getStringType();
final String sensorName = sensor.getName();
if (sensorType == null || sensorName == null) {
continue;
}
if (sensorType.equals(type) && sensorName.equals(name)) {
return sensor;
}
}
return null;
}
@Override
public void setListener(Listener listener) {
synchronized (mLock) {
if (mListener != null) {
throw new RuntimeException("Provider already has a listener set.");
}
mListener = listener;
}
notifySupportedStatesChanged(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED);
notifyDeviceStateChangedIfNeeded();
}
/** Notifies the listener that the set of supported device states has changed. */
private void notifySupportedStatesChanged(@SupportedStatesUpdatedReason int reason) {
List<DeviceState> supportedStates = new ArrayList<>();
Listener listener;
synchronized (mLock) {
if (mListener == null) {
return;
}
listener = mListener;
for (DeviceState deviceState : mOrderedStates) {
if (isThermalStatusCriticalOrAbove(mThermalStatus)
&& deviceState.hasFlag(
DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL)) {
continue;
}
if (mPowerSaveModeEnabled && deviceState.hasFlag(
DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE)) {
continue;
}
supportedStates.add(deviceState);
}
}
listener.onSupportedDeviceStatesChanged(
supportedStates.toArray(new DeviceState[supportedStates.size()]), reason);
}
/** Computes the current device state and notifies the listener of a change, if needed. */
void notifyDeviceStateChangedIfNeeded() {
int stateToReport = INVALID_DEVICE_STATE;
synchronized (mLock) {
if (mListener == null) {
return;
}
int newState = INVALID_DEVICE_STATE;
for (int i = 0; i < mOrderedStates.length; i++) {
int state = mOrderedStates[i].getIdentifier();
if (DEBUG) {
Slog.d(TAG, "Checking conditions for " + mOrderedStates[i].getName() + "("
+ i + ")");
}
boolean conditionSatisfied;
try {
conditionSatisfied = mStateConditions.get(state).getAsBoolean();
} catch (IllegalStateException e) {
// Failed to compute the current state based on current available data. Continue
// with the expectation that notifyDeviceStateChangedIfNeeded() will be called
// when a callback with the missing data is triggered. May trigger another state
// change if another state is satisfied currently.
if (DEBUG) {
Slog.d(TAG, "Unable to check current state", e);
}
continue;
}
if (conditionSatisfied) {
if (DEBUG) {
Slog.d(TAG, "Device State conditions satisfied, transition to " + state);
}
newState = state;
break;
}
}
if (newState == INVALID_DEVICE_STATE) {
Slog.e(TAG, "No declared device states match any of the required conditions.");
dumpSensorValues();
}
if (newState != INVALID_DEVICE_STATE && newState != mLastReportedState) {
mLastReportedState = newState;
stateToReport = newState;
}
}
if (stateToReport != INVALID_DEVICE_STATE) {
mListener.onStateChanged(stateToReport);
}
}
@Override
public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen) {
synchronized (mLock) {
mIsLidOpen = lidOpen;
}
if (DEBUG) {
Slog.d(TAG, "Lid switch state: " + (lidOpen ? "open" : "closed"));
}
notifyDeviceStateChangedIfNeeded();
}
@Override
public void onSensorChanged(SensorEvent event) {
synchronized (mLock) {
mLatestSensorEvent.put(event.sensor, event);
}
notifyDeviceStateChangedIfNeeded();
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// Do nothing.
}
/**
* Implementation of {@link BooleanSupplier} that returns {@code true} if the expected lid
* switch open state matches {@link #mIsLidOpen}.
*/
private final class LidSwitchBooleanSupplier implements BooleanSupplier {
private final boolean mExpectedOpen;
LidSwitchBooleanSupplier(boolean expectedOpen) {
mExpectedOpen = expectedOpen;
}
@Override
public boolean getAsBoolean() {
synchronized (mLock) {
if (mIsLidOpen == null) {
throw new IllegalStateException("Have not received lid switch value.");
}
return mIsLidOpen == mExpectedOpen;
}
}
}
/**
* Implementation of {@link BooleanSupplier} that returns {@code true} if the latest
* {@link SensorEvent#values sensor event values} for the specified {@link Sensor} adhere to
* the supplied {@link NumericRange ranges}.
*/
private final class SensorBooleanSupplier implements BooleanSupplier {
@NonNull
private final Sensor mSensor;
@NonNull
private final List<NumericRange> mExpectedValues;
SensorBooleanSupplier(@NonNull Sensor sensor, @NonNull List<NumericRange> expectedValues) {
mSensor = sensor;
mExpectedValues = expectedValues;
}
@Override
public boolean getAsBoolean() {
synchronized (mLock) {
SensorEvent latestEvent = mLatestSensorEvent.get(mSensor);
if (latestEvent == null) {
throw new IllegalStateException("Have not received sensor event.");
}
if (latestEvent.values.length < mExpectedValues.size()) {
throw new RuntimeException("Number of supplied numeric range(s) does not "
+ "match the number of values in the latest sensor event for sensor: "
+ mSensor);
}
for (int i = 0; i < mExpectedValues.size(); i++) {
if (!adheresToRange(latestEvent.values[i], mExpectedValues.get(i))) {
return false;
}
}
return true;
}
}
/**
* Returns {@code true} if the supplied {@code value} adheres to the constraints specified
* in {@code range}.
*/
private boolean adheresToRange(float value, @NonNull NumericRange range) {
final BigDecimal min = range.getMin_optional();
if (min != null) {
if (DEBUG) {
Slog.d(TAG, "value: " + value + ", constraint min: " + min.floatValue());
}
if (value <= min.floatValue()) {
return false;
}
}
final BigDecimal minInclusive = range.getMinInclusive_optional();
if (minInclusive != null) {
if (DEBUG) {
Slog.d(TAG, "value: " + value + ", constraint min-inclusive: "
+ minInclusive.floatValue());
}
if (value < minInclusive.floatValue()) {
return false;
}
}
final BigDecimal max = range.getMax_optional();
if (max != null) {
if (DEBUG) {
Slog.d(TAG, "value: " + value + ", constraint max: " + max.floatValue());
}
if (value >= max.floatValue()) {
return false;
}
}
final BigDecimal maxInclusive = range.getMaxInclusive_optional();
if (maxInclusive != null) {
if (DEBUG) {
Slog.d(TAG, "value: " + value + ", constraint max-inclusive: "
+ maxInclusive.floatValue());
}
if (value > maxInclusive.floatValue()) {
return false;
}
}
return true;
}
}
/**
* Implementation of {@link BooleanSupplier} whose result is the product of an AND operation
* applied to the result of all child suppliers.
*/
private static final class AndBooleanSupplier implements BooleanSupplier {
@NonNull
List<BooleanSupplier> mBooleanSuppliers;
AndBooleanSupplier(@NonNull List<BooleanSupplier> booleanSuppliers) {
mBooleanSuppliers = booleanSuppliers;
}
@Override
public boolean getAsBoolean() {
for (int i = 0; i < mBooleanSuppliers.size(); i++) {
if (!mBooleanSuppliers.get(i).getAsBoolean()) {
return false;
}
}
return true;
}
}
/**
* Returns the device state configuration file that should be used, or {@code null} if no file
* is present on the device.
* <p>
* Defaults to returning a config file present in the data/ dir at
* {@link #DATA_CONFIG_FILE_PATH}, and then falls back to the config file in the vendor/ dir
* at {@link #VENDOR_CONFIG_FILE_PATH} if no config file is found in the data/ dir.
*/
@Nullable
private static File getConfigurationFile() {
final File configFileFromDataDir = Environment.buildPath(Environment.getDataDirectory(),
DATA_CONFIG_FILE_PATH, CONFIG_FILE_NAME);
if (configFileFromDataDir.exists()) {
return configFileFromDataDir;
}
final File configFileFromVendorDir = Environment.buildPath(Environment.getVendorDirectory(),
VENDOR_CONFIG_FILE_PATH, CONFIG_FILE_NAME);
if (configFileFromVendorDir.exists()) {
return configFileFromVendorDir;
}
return null;
}
@GuardedBy("mLock")
private void dumpSensorValues() {
Slog.i(TAG, "Sensor values:");
for (Sensor sensor : mLatestSensorEvent.keySet()) {
SensorEvent sensorEvent = mLatestSensorEvent.get(sensor);
if (sensorEvent != null) {
Slog.i(TAG, sensor.getName() + ": " + Arrays.toString(sensorEvent.values));
} else {
Slog.i(TAG, sensor.getName() + ": null");
}
}
}
/**
* Tries to parse the provided file into a {@link DeviceStateConfig} object. Returns
* {@code null} if the file could not be successfully parsed.
*/
@Nullable
private static DeviceStateConfig parseConfig(@NonNull ReadableConfig readableConfig) {
try (InputStream in = readableConfig.openRead();
InputStream bin = new BufferedInputStream(in)) {
return XmlParser.read(bin);
} catch (IOException | DatatypeConfigurationException | XmlPullParserException e) {
Slog.e(TAG, "Encountered an error while reading device state config", e);
}
return null;
}
/** Implementation of {@link ReadableConfig} that reads config data from a file. */
private static final class ReadableFileConfig implements ReadableConfig {
@NonNull
private final File mFile;
private ReadableFileConfig(@NonNull File file) {
mFile = file;
}
@Override
public InputStream openRead() throws IOException {
return new FileInputStream(mFile);
}
}
@VisibleForTesting
void onPowerSaveModeChanged(boolean isPowerSaveModeEnabled) {
synchronized (mLock) {
if (mPowerSaveModeEnabled != isPowerSaveModeEnabled) {
mPowerSaveModeEnabled = isPowerSaveModeEnabled;
notifySupportedStatesChanged(
isPowerSaveModeEnabled ? SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_ENABLED
: SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_DISABLED);
}
}
}
@Override
public void onThermalStatusChanged(@PowerManager.ThermalStatus int thermalStatus) {
int previousThermalStatus;
synchronized (mLock) {
previousThermalStatus = mThermalStatus;
mThermalStatus = thermalStatus;
}
boolean isThermalStatusCriticalOrAbove = isThermalStatusCriticalOrAbove(thermalStatus);
boolean isPreviousThermalStatusCriticalOrAbove =
isThermalStatusCriticalOrAbove(previousThermalStatus);
if (isThermalStatusCriticalOrAbove != isPreviousThermalStatusCriticalOrAbove) {
Slog.i(TAG, "Updating supported device states due to thermal status change."
+ " isThermalStatusCriticalOrAbove: " + isThermalStatusCriticalOrAbove);
notifySupportedStatesChanged(
isThermalStatusCriticalOrAbove
? SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL
: SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_NORMAL);
}
}
private static boolean isThermalStatusCriticalOrAbove(
@PowerManager.ThermalStatus int thermalStatus) {
switch (thermalStatus) {
case PowerManager.THERMAL_STATUS_CRITICAL:
case PowerManager.THERMAL_STATUS_EMERGENCY:
case PowerManager.THERMAL_STATUS_SHUTDOWN:
return true;
default:
return false;
}
}
private static boolean hasThermalSensitiveState(List<DeviceState> deviceStates) {
for (DeviceState state : deviceStates) {
if (state.hasFlag(DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL)) {
return true;
}
}
return false;
}
private static boolean hasPowerSaveSensitiveState(List<DeviceState> deviceStates) {
for (int i = 0; i < deviceStates.size(); i++) {
if (deviceStates.get(i).hasFlag(DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE)) {
return true;
}
}
return false;
}
}