diff --git a/services/core/java/com/android/server/devicestate/DeviceState.java b/services/core/java/com/android/server/devicestate/DeviceState.java index 6baa1bd2ae20c..a589313a1e921 100644 --- a/services/core/java/com/android/server/devicestate/DeviceState.java +++ b/services/core/java/com/android/server/devicestate/DeviceState.java @@ -72,12 +72,19 @@ public final class DeviceState { */ public static final int FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP = 1 << 3; + /** + * This flag indicates that the corresponding state should be disabled when the device is + * overheating and reaching the critical status. + */ + public static final int FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL = 1 << 4; + /** @hide */ @IntDef(prefix = {"FLAG_"}, flag = true, value = { FLAG_CANCEL_OVERRIDE_REQUESTS, FLAG_APP_INACCESSIBLE, FLAG_EMULATED_ONLY, - FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP + FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP, + FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL }) @Retention(RetentionPolicy.SOURCE) public @interface DeviceStateFlags {} diff --git a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java index f6497d7f8091b..7f733efef8a23 100644 --- a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java +++ b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java @@ -27,6 +27,7 @@ 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; @@ -80,7 +81,8 @@ import javax.xml.datatype.DatatypeConfigurationException; * provided. */ public final class DeviceStateProviderImpl implements DeviceStateProvider, - InputManagerInternal.LidSwitchCallback, SensorEventListener { + InputManagerInternal.LidSwitchCallback, SensorEventListener, + PowerManager.OnThermalStatusChangedListener { private static final String TAG = "DeviceStateProviderImpl"; private static final boolean DEBUG = false; @@ -99,6 +101,8 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider, 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_DISABLE_WHEN_THERMAL_STATUS_CRITICAL = + "FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL"; /** Interface that allows reading the device state configuration. */ interface ReadableConfig { @@ -158,6 +162,9 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider, case FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP: flags |= DeviceState.FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP; break; + case FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL: + flags |= DeviceState.FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL; + break; default: Slog.w(TAG, "Parsed unknown flag with name: " + configFlagString); @@ -200,6 +207,8 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider, private Boolean mIsLidOpen; @GuardedBy("mLock") private final Map mLatestSensorEvent = new ArrayMap<>(); + @GuardedBy("mLock") + private @PowerManager.ThermalStatus int mThermalStatus = PowerManager.THERMAL_STATUS_NONE; private DeviceStateProviderImpl(@NonNull Context context, @NonNull List deviceStates, @@ -214,6 +223,16 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider, mOrderedStates = orderedStates; setStateConditions(deviceStates, stateConditions); + + // 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 powerManager = context.getSystemService(PowerManager.class); + if (powerManager != null) { + powerManager.addThermalStatusListener(this); + } + } } private void setStateConditions(@NonNull List deviceStates, @@ -353,16 +372,25 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider, /** Notifies the listener that the set of supported device states has changed. */ private void notifySupportedStatesChanged() { - DeviceState[] supportedStates; + List supportedStates = new ArrayList<>(); + Listener listener; synchronized (mLock) { if (mListener == null) { return; } - - supportedStates = Arrays.copyOf(mOrderedStates, mOrderedStates.length); + listener = mListener; + for (DeviceState deviceState : mOrderedStates) { + if (isThermalStatusCriticalOrAbove(mThermalStatus) + && deviceState.hasFlag( + DeviceState.FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL)) { + continue; + } + supportedStates.add(deviceState); + } } - mListener.onSupportedDeviceStatesChanged(supportedStates); + listener.onSupportedDeviceStatesChanged( + supportedStates.toArray(new DeviceState[supportedStates.size()])); } /** Computes the current device state and notifies the listener of a change, if needed. */ @@ -645,4 +673,43 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider, return new FileInputStream(mFile); } } + + @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(); + } + } + + 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 deviceStates) { + for (DeviceState state : deviceStates) { + if (state.hasFlag(DeviceState.FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL)) { + return true; + } + } + return false; + } } diff --git a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java index 261156611a06f..7fac9b6081141 100644 --- a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java +++ b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java @@ -35,6 +35,7 @@ import android.content.Context; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorManager; +import android.os.PowerManager; import androidx.annotation.NonNull; @@ -312,6 +313,14 @@ public final class DeviceStateProviderImplTest { + " \n" + " \n" + " \n" + + " \n" + + " 4\n" + + " THERMAL_TEST\n" + + " \n" + + " FLAG_EMULATED_ONLY\n" + + " FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL\n" + + " \n" + + " \n" + "\n"; DeviceStateProviderImpl.ReadableConfig config = new TestReadableConfig(configString); return DeviceStateProviderImpl.createFromConfig(mContext, @@ -332,7 +341,10 @@ public final class DeviceStateProviderImplTest { new DeviceState[]{ new DeviceState(1, "CLOSED", 0 /* flags */), new DeviceState(2, "HALF_OPENED", 0 /* flags */), - new DeviceState(3, "OPENED", 0 /* flags */) }, + new DeviceState(3, "OPENED", 0 /* flags */), + new DeviceState(4, "THERMAL_TEST", + DeviceState.FLAG_EMULATED_ONLY + | DeviceState.FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL) }, mDeviceStateArrayCaptor.getValue()); // onStateChanged() should not be called because the provider has not yet been notified of // the initial sensor state. @@ -375,6 +387,57 @@ public final class DeviceStateProviderImplTest { assertEquals(1, mIntegerCaptor.getValue().intValue()); } + @Test + public void test_flagDisableWhenThermalStatusCritical() throws Exception { + Sensor sensor = newSensor("sensor", Sensor.STRING_TYPE_HINGE_ANGLE); + when(mSensorManager.getSensorList(anyInt())).thenReturn(List.of(sensor)); + DeviceStateProviderImpl provider = create_sensorBasedProvider(sensor); + + provider.onThermalStatusChanged(PowerManager.THERMAL_STATUS_LIGHT); + DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class); + provider.setListener(listener); + + verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture()); + assertArrayEquals( + new DeviceState[]{ + new DeviceState(1, "CLOSED", 0 /* flags */), + new DeviceState(2, "HALF_OPENED", 0 /* flags */), + new DeviceState(3, "OPENED", 0 /* flags */), + new DeviceState(4, "THERMAL_TEST", + DeviceState.FLAG_EMULATED_ONLY + | DeviceState.FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL) }, + mDeviceStateArrayCaptor.getValue()); + Mockito.clearInvocations(listener); + + provider.onThermalStatusChanged(PowerManager.THERMAL_STATUS_MODERATE); + verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture()); + Mockito.clearInvocations(listener); + + // The THERMAL_TEST state should be disabled. + provider.onThermalStatusChanged(PowerManager.THERMAL_STATUS_CRITICAL); + verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture()); + assertArrayEquals( + new DeviceState[]{ + new DeviceState(1, "CLOSED", 0 /* flags */), + new DeviceState(2, "HALF_OPENED", 0 /* flags */), + new DeviceState(3, "OPENED", 0 /* flags */) }, + mDeviceStateArrayCaptor.getValue()); + Mockito.clearInvocations(listener); + + // The THERMAL_TEST state should be re-enabled. + provider.onThermalStatusChanged(PowerManager.THERMAL_STATUS_LIGHT); + verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture()); + assertArrayEquals( + new DeviceState[]{ + new DeviceState(1, "CLOSED", 0 /* flags */), + new DeviceState(2, "HALF_OPENED", 0 /* flags */), + new DeviceState(3, "OPENED", 0 /* flags */), + new DeviceState(4, "THERMAL_TEST", + DeviceState.FLAG_EMULATED_ONLY + | DeviceState.FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL) }, + mDeviceStateArrayCaptor.getValue()); + } + @Test public void test_invalidSensorValues() throws Exception { // onStateChanged() should not be triggered by invalid sensor values.