Loading core/api/current.txt +1 −0 Original line number Diff line number Diff line Loading @@ -33039,6 +33039,7 @@ package android.os { method public int getCurrentThermalStatus(); method public int getLocationPowerSaveMode(); method public float getThermalHeadroom(@IntRange(from=0, to=60) int); method @FlaggedApi("android.os.allow_thermal_headroom_thresholds") @NonNull public java.util.Map<java.lang.Integer,java.lang.Float> getThermalHeadroomThresholds(); method public boolean isAllowedInLowPowerStandby(int); method public boolean isAllowedInLowPowerStandby(@NonNull String); method public boolean isBatteryDischargePredictionPersonalized(); core/java/android/os/IThermalService.aidl +5 −0 Original line number Diff line number Diff line Loading @@ -111,4 +111,9 @@ interface IThermalService { * occur; returns NaN if the headroom or forecast is unavailable */ float getThermalHeadroom(int forecastSeconds); /** * @return thermal headroom for each thermal status */ float[] getThermalHeadroomThresholds(); } core/java/android/os/PowerManager.java +64 −0 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package android.os; import android.Manifest.permission; import android.annotation.CallbackExecutor; import android.annotation.CurrentTimeMillisLong; import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; Loading @@ -41,14 +42,17 @@ import android.view.Display; import com.android.internal.util.Preconditions; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.net.InetAddress; import java.net.UnknownHostException; import java.time.Duration; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.Executor; Loading Loading @@ -1179,6 +1183,8 @@ public final class PowerManager { private final ArrayMap<OnThermalStatusChangedListener, IThermalStatusListener> mListenerMap = new ArrayMap<>(); private final Object mThermalHeadroomThresholdsLock = new Object(); private float[] mThermalHeadroomThresholds = null; /** * {@hide} Loading Loading @@ -2634,6 +2640,7 @@ public final class PowerManager { public static final int THERMAL_STATUS_SHUTDOWN = Temperature.THROTTLING_SHUTDOWN; /** @hide */ @Target(ElementType.TYPE_USE) @IntDef(prefix = { "THERMAL_STATUS_" }, value = { THERMAL_STATUS_NONE, THERMAL_STATUS_LIGHT, Loading Loading @@ -2797,6 +2804,63 @@ public final class PowerManager { } } /** * Gets the thermal headroom thresholds for all available thermal throttling status above * {@link #THERMAL_STATUS_NONE}. * <p> * A thermal status key in the returned map is only set if the device manufacturer has the * corresponding threshold defined for at least one of its sensors. If it's set, one should * expect to see that from {@link #getCurrentThermalStatus()} or * {@link OnThermalStatusChangedListener#onThermalStatusChanged(int)}. * <p> * The headroom threshold is used to interpret the possible thermal throttling status based on * the headroom prediction. For example, if the headroom threshold for * {@link #THERMAL_STATUS_LIGHT} is 0.7, and a headroom prediction in 10s returns 0.75 * (or {@code getThermalHeadroom(10)=0.75}), one can expect that in 10 seconds the system could * be in lightly throttled state if the workload remains the same. The app can consider * taking actions according to the nearest throttling status the difference between the headroom * and the threshold. * <p> * For new devices it's guaranteed to have a single sensor, but for older devices with multiple * sensors reporting different threshold values, the minimum threshold is taken to be * conservative on predictions. Thus, when reading real-time headroom, it's not guaranteed that * a real-time value of 0.75 (or {@code getThermalHeadroom(0)}=0.75) exceeding the threshold of * 0.7 above will always come with lightly throttled state * (or {@code getCurrentThermalStatus()=THERMAL_STATUS_LIGHT}) but it can be lower * (or {@code getCurrentThermalStatus()=THERMAL_STATUS_NONE}). While it's always guaranteed that * the device won't be throttled heavier than the unmet threshold's state, so a real-time * headroom of 0.75 will never come with {@link #THERMAL_STATUS_MODERATE} but lower, and 0.65 * will never come with {@link #THERMAL_STATUS_LIGHT} but {@link #THERMAL_STATUS_NONE}. * <p> * The returned map of thresholds will not change between calls to this function, so it's * best to call this once on initialization. Modifying the result will not change the thresholds * cached by the system, and a new call to the API will get a new copy. * * @return map from each thermal status to its thermal headroom * @throws IllegalStateException if the thermal service is not ready * @throws UnsupportedOperationException if the feature is not enabled */ @FlaggedApi(Flags.FLAG_ALLOW_THERMAL_HEADROOM_THRESHOLDS) public @NonNull Map<@ThermalStatus Integer, Float> getThermalHeadroomThresholds() { try { synchronized (mThermalHeadroomThresholdsLock) { if (mThermalHeadroomThresholds == null) { mThermalHeadroomThresholds = mThermalService.getThermalHeadroomThresholds(); } final ArrayMap<Integer, Float> ret = new ArrayMap<>(THERMAL_STATUS_SHUTDOWN); for (int status = THERMAL_STATUS_LIGHT; status <= THERMAL_STATUS_SHUTDOWN; status++) { if (!Float.isNaN(mThermalHeadroomThresholds[status])) { ret.put(status, mThermalHeadroomThresholds[status]); } } return ret; } } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * If true, the doze component is not started until after the screen has been * turned off and the screen off animation has been performed. Loading core/java/android/os/flags.aconfig +7 −0 Original line number Diff line number Diff line Loading @@ -21,6 +21,13 @@ flag { bug: "297542292" } flag { name: "allow_thermal_headroom_thresholds" namespace: "game" description: "Enable thermal headroom thresholds API" bug: "288119641" } flag { name: "allow_private_profile" namespace: "profile_experiences" Loading services/core/java/com/android/server/power/ThermalManagerService.java +70 −15 Original line number Diff line number Diff line Loading @@ -28,6 +28,7 @@ import android.hardware.thermal.V1_0.ThermalStatusCode; import android.hardware.thermal.V1_1.IThermalCallback; import android.os.Binder; import android.os.CoolingDevice; import android.os.Flags; import android.os.Handler; import android.os.HwBinder; import android.os.IBinder; Loading Loading @@ -181,7 +182,7 @@ public class ThermalManagerService extends SystemService { onTemperatureChanged(temperatures.get(i), false); } onTemperatureMapChangedLocked(); mTemperatureWatcher.updateSevereThresholds(); mTemperatureWatcher.updateThresholds(); mHalReady.set(true); } } Loading Loading @@ -505,6 +506,20 @@ public class ThermalManagerService extends SystemService { return mTemperatureWatcher.getForecast(forecastSeconds); } @Override public float[] getThermalHeadroomThresholds() { if (!mHalReady.get()) { throw new IllegalStateException("Thermal HAL connection is not initialized"); } if (!Flags.allowThermalHeadroomThresholds()) { throw new UnsupportedOperationException("Thermal headroom thresholds not enabled"); } synchronized (mTemperatureWatcher.mSamples) { return Arrays.copyOf(mTemperatureWatcher.mHeadroomThresholds, mTemperatureWatcher.mHeadroomThresholds.length); } } @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { dumpInternal(fd, pw, args); Loading Loading @@ -580,6 +595,12 @@ public class ThermalManagerService extends SystemService { mHalWrapper.getTemperatureThresholds(false, 0)); } } if (Flags.allowThermalHeadroomThresholds()) { synchronized (mTemperatureWatcher.mSamples) { pw.println("Temperature headroom thresholds:"); pw.println(Arrays.toString(mTemperatureWatcher.mHeadroomThresholds)); } } } finally { Binder.restoreCallingIdentity(token); } Loading Loading @@ -964,7 +985,14 @@ public class ThermalManagerService extends SystemService { connectToHal(); } if (mInstance != null) { Slog.i(TAG, "Thermal HAL AIDL service connected."); try { Slog.i(TAG, "Thermal HAL AIDL service connected with version " + mInstance.getInterfaceVersion()); } catch (RemoteException e) { Slog.e(TAG, "Unable to read interface version from Thermal HAL", e); connectToHal(); return; } registerThermalChangedCallback(); } } Loading Loading @@ -1439,6 +1467,8 @@ public class ThermalManagerService extends SystemService { @VisibleForTesting ArrayMap<String, Float> mSevereThresholds = new ArrayMap<>(); @GuardedBy("mSamples") float[] mHeadroomThresholds = new float[ThrottlingSeverity.SHUTDOWN + 1]; @GuardedBy("mSamples") private long mLastForecastCallTimeMillis = 0; Loading @@ -1446,20 +1476,47 @@ public class ThermalManagerService extends SystemService { @VisibleForTesting long mInactivityThresholdMillis = INACTIVITY_THRESHOLD_MILLIS; void updateSevereThresholds() { void updateThresholds() { synchronized (mSamples) { List<TemperatureThreshold> thresholds = mHalWrapper.getTemperatureThresholds(true, Temperature.TYPE_SKIN); if (Flags.allowThermalHeadroomThresholds()) { Arrays.fill(mHeadroomThresholds, Float.NaN); } for (int t = 0; t < thresholds.size(); ++t) { TemperatureThreshold threshold = thresholds.get(t); if (threshold.hotThrottlingThresholds.length <= ThrottlingSeverity.SEVERE) { continue; } float temperature = float severeThreshold = threshold.hotThrottlingThresholds[ThrottlingSeverity.SEVERE]; if (!Float.isNaN(temperature)) { mSevereThresholds.put(threshold.name, threshold.hotThrottlingThresholds[ThrottlingSeverity.SEVERE]); if (!Float.isNaN(severeThreshold)) { mSevereThresholds.put(threshold.name, severeThreshold); for (int severity = ThrottlingSeverity.LIGHT; severity <= ThrottlingSeverity.SHUTDOWN; severity++) { if (Flags.allowThermalHeadroomThresholds() && threshold.hotThrottlingThresholds.length > severity) { updateHeadroomThreshold(severity, threshold.hotThrottlingThresholds[severity], severeThreshold); } } } } } } // For a older device with multiple SKIN sensors, we will set a severity's headroom // threshold based on the minimum value of all as a workaround. void updateHeadroomThreshold(int severity, float threshold, float severeThreshold) { if (!Float.isNaN(threshold)) { synchronized (mSamples) { float headroom = normalizeTemperature(threshold, severeThreshold); if (Float.isNaN(mHeadroomThresholds[severity])) { mHeadroomThresholds[severity] = headroom; } else { float lastHeadroom = mHeadroomThresholds[severity]; mHeadroomThresholds[severity] = Math.min(lastHeadroom, headroom); } } } Loading Loading @@ -1541,8 +1598,7 @@ public class ThermalManagerService extends SystemService { private static final float DEGREES_BETWEEN_ZERO_AND_ONE = 30.0f; @VisibleForTesting float normalizeTemperature(float temperature, float severeThreshold) { synchronized (mSamples) { static float normalizeTemperature(float temperature, float severeThreshold) { float zeroNormalized = severeThreshold - DEGREES_BETWEEN_ZERO_AND_ONE; if (temperature <= zeroNormalized) { return 0.0f; Loading @@ -1550,7 +1606,6 @@ public class ThermalManagerService extends SystemService { float delta = temperature - zeroNormalized; return delta / DEGREES_BETWEEN_ZERO_AND_ONE; } } private static final int MINIMUM_SAMPLE_COUNT = 3; Loading Loading
core/api/current.txt +1 −0 Original line number Diff line number Diff line Loading @@ -33039,6 +33039,7 @@ package android.os { method public int getCurrentThermalStatus(); method public int getLocationPowerSaveMode(); method public float getThermalHeadroom(@IntRange(from=0, to=60) int); method @FlaggedApi("android.os.allow_thermal_headroom_thresholds") @NonNull public java.util.Map<java.lang.Integer,java.lang.Float> getThermalHeadroomThresholds(); method public boolean isAllowedInLowPowerStandby(int); method public boolean isAllowedInLowPowerStandby(@NonNull String); method public boolean isBatteryDischargePredictionPersonalized();
core/java/android/os/IThermalService.aidl +5 −0 Original line number Diff line number Diff line Loading @@ -111,4 +111,9 @@ interface IThermalService { * occur; returns NaN if the headroom or forecast is unavailable */ float getThermalHeadroom(int forecastSeconds); /** * @return thermal headroom for each thermal status */ float[] getThermalHeadroomThresholds(); }
core/java/android/os/PowerManager.java +64 −0 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package android.os; import android.Manifest.permission; import android.annotation.CallbackExecutor; import android.annotation.CurrentTimeMillisLong; import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; Loading @@ -41,14 +42,17 @@ import android.view.Display; import com.android.internal.util.Preconditions; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.net.InetAddress; import java.net.UnknownHostException; import java.time.Duration; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.Executor; Loading Loading @@ -1179,6 +1183,8 @@ public final class PowerManager { private final ArrayMap<OnThermalStatusChangedListener, IThermalStatusListener> mListenerMap = new ArrayMap<>(); private final Object mThermalHeadroomThresholdsLock = new Object(); private float[] mThermalHeadroomThresholds = null; /** * {@hide} Loading Loading @@ -2634,6 +2640,7 @@ public final class PowerManager { public static final int THERMAL_STATUS_SHUTDOWN = Temperature.THROTTLING_SHUTDOWN; /** @hide */ @Target(ElementType.TYPE_USE) @IntDef(prefix = { "THERMAL_STATUS_" }, value = { THERMAL_STATUS_NONE, THERMAL_STATUS_LIGHT, Loading Loading @@ -2797,6 +2804,63 @@ public final class PowerManager { } } /** * Gets the thermal headroom thresholds for all available thermal throttling status above * {@link #THERMAL_STATUS_NONE}. * <p> * A thermal status key in the returned map is only set if the device manufacturer has the * corresponding threshold defined for at least one of its sensors. If it's set, one should * expect to see that from {@link #getCurrentThermalStatus()} or * {@link OnThermalStatusChangedListener#onThermalStatusChanged(int)}. * <p> * The headroom threshold is used to interpret the possible thermal throttling status based on * the headroom prediction. For example, if the headroom threshold for * {@link #THERMAL_STATUS_LIGHT} is 0.7, and a headroom prediction in 10s returns 0.75 * (or {@code getThermalHeadroom(10)=0.75}), one can expect that in 10 seconds the system could * be in lightly throttled state if the workload remains the same. The app can consider * taking actions according to the nearest throttling status the difference between the headroom * and the threshold. * <p> * For new devices it's guaranteed to have a single sensor, but for older devices with multiple * sensors reporting different threshold values, the minimum threshold is taken to be * conservative on predictions. Thus, when reading real-time headroom, it's not guaranteed that * a real-time value of 0.75 (or {@code getThermalHeadroom(0)}=0.75) exceeding the threshold of * 0.7 above will always come with lightly throttled state * (or {@code getCurrentThermalStatus()=THERMAL_STATUS_LIGHT}) but it can be lower * (or {@code getCurrentThermalStatus()=THERMAL_STATUS_NONE}). While it's always guaranteed that * the device won't be throttled heavier than the unmet threshold's state, so a real-time * headroom of 0.75 will never come with {@link #THERMAL_STATUS_MODERATE} but lower, and 0.65 * will never come with {@link #THERMAL_STATUS_LIGHT} but {@link #THERMAL_STATUS_NONE}. * <p> * The returned map of thresholds will not change between calls to this function, so it's * best to call this once on initialization. Modifying the result will not change the thresholds * cached by the system, and a new call to the API will get a new copy. * * @return map from each thermal status to its thermal headroom * @throws IllegalStateException if the thermal service is not ready * @throws UnsupportedOperationException if the feature is not enabled */ @FlaggedApi(Flags.FLAG_ALLOW_THERMAL_HEADROOM_THRESHOLDS) public @NonNull Map<@ThermalStatus Integer, Float> getThermalHeadroomThresholds() { try { synchronized (mThermalHeadroomThresholdsLock) { if (mThermalHeadroomThresholds == null) { mThermalHeadroomThresholds = mThermalService.getThermalHeadroomThresholds(); } final ArrayMap<Integer, Float> ret = new ArrayMap<>(THERMAL_STATUS_SHUTDOWN); for (int status = THERMAL_STATUS_LIGHT; status <= THERMAL_STATUS_SHUTDOWN; status++) { if (!Float.isNaN(mThermalHeadroomThresholds[status])) { ret.put(status, mThermalHeadroomThresholds[status]); } } return ret; } } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * If true, the doze component is not started until after the screen has been * turned off and the screen off animation has been performed. Loading
core/java/android/os/flags.aconfig +7 −0 Original line number Diff line number Diff line Loading @@ -21,6 +21,13 @@ flag { bug: "297542292" } flag { name: "allow_thermal_headroom_thresholds" namespace: "game" description: "Enable thermal headroom thresholds API" bug: "288119641" } flag { name: "allow_private_profile" namespace: "profile_experiences" Loading
services/core/java/com/android/server/power/ThermalManagerService.java +70 −15 Original line number Diff line number Diff line Loading @@ -28,6 +28,7 @@ import android.hardware.thermal.V1_0.ThermalStatusCode; import android.hardware.thermal.V1_1.IThermalCallback; import android.os.Binder; import android.os.CoolingDevice; import android.os.Flags; import android.os.Handler; import android.os.HwBinder; import android.os.IBinder; Loading Loading @@ -181,7 +182,7 @@ public class ThermalManagerService extends SystemService { onTemperatureChanged(temperatures.get(i), false); } onTemperatureMapChangedLocked(); mTemperatureWatcher.updateSevereThresholds(); mTemperatureWatcher.updateThresholds(); mHalReady.set(true); } } Loading Loading @@ -505,6 +506,20 @@ public class ThermalManagerService extends SystemService { return mTemperatureWatcher.getForecast(forecastSeconds); } @Override public float[] getThermalHeadroomThresholds() { if (!mHalReady.get()) { throw new IllegalStateException("Thermal HAL connection is not initialized"); } if (!Flags.allowThermalHeadroomThresholds()) { throw new UnsupportedOperationException("Thermal headroom thresholds not enabled"); } synchronized (mTemperatureWatcher.mSamples) { return Arrays.copyOf(mTemperatureWatcher.mHeadroomThresholds, mTemperatureWatcher.mHeadroomThresholds.length); } } @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { dumpInternal(fd, pw, args); Loading Loading @@ -580,6 +595,12 @@ public class ThermalManagerService extends SystemService { mHalWrapper.getTemperatureThresholds(false, 0)); } } if (Flags.allowThermalHeadroomThresholds()) { synchronized (mTemperatureWatcher.mSamples) { pw.println("Temperature headroom thresholds:"); pw.println(Arrays.toString(mTemperatureWatcher.mHeadroomThresholds)); } } } finally { Binder.restoreCallingIdentity(token); } Loading Loading @@ -964,7 +985,14 @@ public class ThermalManagerService extends SystemService { connectToHal(); } if (mInstance != null) { Slog.i(TAG, "Thermal HAL AIDL service connected."); try { Slog.i(TAG, "Thermal HAL AIDL service connected with version " + mInstance.getInterfaceVersion()); } catch (RemoteException e) { Slog.e(TAG, "Unable to read interface version from Thermal HAL", e); connectToHal(); return; } registerThermalChangedCallback(); } } Loading Loading @@ -1439,6 +1467,8 @@ public class ThermalManagerService extends SystemService { @VisibleForTesting ArrayMap<String, Float> mSevereThresholds = new ArrayMap<>(); @GuardedBy("mSamples") float[] mHeadroomThresholds = new float[ThrottlingSeverity.SHUTDOWN + 1]; @GuardedBy("mSamples") private long mLastForecastCallTimeMillis = 0; Loading @@ -1446,20 +1476,47 @@ public class ThermalManagerService extends SystemService { @VisibleForTesting long mInactivityThresholdMillis = INACTIVITY_THRESHOLD_MILLIS; void updateSevereThresholds() { void updateThresholds() { synchronized (mSamples) { List<TemperatureThreshold> thresholds = mHalWrapper.getTemperatureThresholds(true, Temperature.TYPE_SKIN); if (Flags.allowThermalHeadroomThresholds()) { Arrays.fill(mHeadroomThresholds, Float.NaN); } for (int t = 0; t < thresholds.size(); ++t) { TemperatureThreshold threshold = thresholds.get(t); if (threshold.hotThrottlingThresholds.length <= ThrottlingSeverity.SEVERE) { continue; } float temperature = float severeThreshold = threshold.hotThrottlingThresholds[ThrottlingSeverity.SEVERE]; if (!Float.isNaN(temperature)) { mSevereThresholds.put(threshold.name, threshold.hotThrottlingThresholds[ThrottlingSeverity.SEVERE]); if (!Float.isNaN(severeThreshold)) { mSevereThresholds.put(threshold.name, severeThreshold); for (int severity = ThrottlingSeverity.LIGHT; severity <= ThrottlingSeverity.SHUTDOWN; severity++) { if (Flags.allowThermalHeadroomThresholds() && threshold.hotThrottlingThresholds.length > severity) { updateHeadroomThreshold(severity, threshold.hotThrottlingThresholds[severity], severeThreshold); } } } } } } // For a older device with multiple SKIN sensors, we will set a severity's headroom // threshold based on the minimum value of all as a workaround. void updateHeadroomThreshold(int severity, float threshold, float severeThreshold) { if (!Float.isNaN(threshold)) { synchronized (mSamples) { float headroom = normalizeTemperature(threshold, severeThreshold); if (Float.isNaN(mHeadroomThresholds[severity])) { mHeadroomThresholds[severity] = headroom; } else { float lastHeadroom = mHeadroomThresholds[severity]; mHeadroomThresholds[severity] = Math.min(lastHeadroom, headroom); } } } Loading Loading @@ -1541,8 +1598,7 @@ public class ThermalManagerService extends SystemService { private static final float DEGREES_BETWEEN_ZERO_AND_ONE = 30.0f; @VisibleForTesting float normalizeTemperature(float temperature, float severeThreshold) { synchronized (mSamples) { static float normalizeTemperature(float temperature, float severeThreshold) { float zeroNormalized = severeThreshold - DEGREES_BETWEEN_ZERO_AND_ONE; if (temperature <= zeroNormalized) { return 0.0f; Loading @@ -1550,7 +1606,6 @@ public class ThermalManagerService extends SystemService { float delta = temperature - zeroNormalized; return delta / DEGREES_BETWEEN_ZERO_AND_ONE; } } private static final int MINIMUM_SAMPLE_COUNT = 3; Loading