Loading api/current.txt +1 −0 Original line number Diff line number Diff line Loading @@ -36633,6 +36633,7 @@ package android.os { method public void addThermalStatusListener(@NonNull java.util.concurrent.Executor, @NonNull android.os.PowerManager.OnThermalStatusChangedListener); method public int getCurrentThermalStatus(); method public int getLocationPowerSaveMode(); method public float getThermalHeadroom(@IntRange(from=0, to=60) int); method public boolean isDeviceIdleMode(); method public boolean isIgnoringBatteryOptimizations(String); method public boolean isInteractive(); core/java/android/app/SystemServiceRegistry.java +7 −4 Original line number Diff line number Diff line Loading @@ -148,6 +148,7 @@ import android.os.IHardwarePropertiesManager; import android.os.IPowerManager; import android.os.IRecoverySystem; import android.os.ISystemUpdateManager; import android.os.IThermalService; import android.os.IUserManager; import android.os.IncidentManager; import android.os.PowerManager; Loading Loading @@ -576,10 +577,12 @@ public final class SystemServiceRegistry { new CachedServiceFetcher<PowerManager>() { @Override public PowerManager createService(ContextImpl ctx) throws ServiceNotFoundException { IBinder b = ServiceManager.getServiceOrThrow(Context.POWER_SERVICE); IPowerManager service = IPowerManager.Stub.asInterface(b); return new PowerManager(ctx.getOuterContext(), service, ctx.mMainThread.getHandler()); IBinder powerBinder = ServiceManager.getServiceOrThrow(Context.POWER_SERVICE); IPowerManager powerService = IPowerManager.Stub.asInterface(powerBinder); IBinder thermalBinder = ServiceManager.getServiceOrThrow(Context.THERMAL_SERVICE); IThermalService thermalService = IThermalService.Stub.asInterface(thermalBinder); return new PowerManager(ctx.getOuterContext(), powerService, thermalService, ctx.mMainThread.getHandler()); }}); registerService(Context.RECOVERY_SERVICE, RecoverySystem.class, Loading core/java/android/os/IThermalService.aidl +7 −0 Original line number Diff line number Diff line Loading @@ -103,4 +103,11 @@ interface IThermalService { * {@hide} */ List<CoolingDevice> getCurrentCoolingDevicesWithType(in int type); /** * @param forecastSeconds how many seconds ahead to forecast the provided headroom * @return forecasted thermal headroom, normalized such that 1.0 indicates that throttling will * occur; returns NaN if the headroom or forecast is unavailable */ float getThermalHeadroom(int forecastSeconds); } core/java/android/os/PowerManager.java +98 −62 Original line number Diff line number Diff line Loading @@ -18,7 +18,9 @@ package android.os; import android.Manifest.permission; import android.annotation.CallbackExecutor; import android.annotation.CurrentTimeMillisLong; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; Loading @@ -39,6 +41,7 @@ import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicLong; /** * This class gives you control of the power state of the device. Loading Loading @@ -916,20 +919,22 @@ public final class PowerManager { final IPowerManager mService; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) final Handler mHandler; final IThermalService mThermalService; /** We lazily initialize it.*/ private DeviceIdleManager mDeviceIdleManager; IThermalService mThermalService; private final ArrayMap<OnThermalStatusChangedListener, IThermalStatusListener> mListenerMap = new ArrayMap<>(); /** * {@hide} */ public PowerManager(Context context, IPowerManager service, Handler handler) { public PowerManager(Context context, IPowerManager service, IThermalService thermalService, Handler handler) { mContext = context; mService = service; mThermalService = thermalService; mHandler = handler; } Loading Loading @@ -1877,11 +1882,6 @@ public final class PowerManager { * thermal throttling. */ public @ThermalStatus int getCurrentThermalStatus() { synchronized (this) { if (mThermalService == null) { mThermalService = IThermalService.Stub.asInterface( ServiceManager.getService(Context.THERMAL_SERVICE)); } try { return mThermalService.getCurrentThermalStatus(); } catch (RemoteException e) { Loading @@ -1889,8 +1889,6 @@ public final class PowerManager { } } } /** * Listener passed to * {@link PowerManager#addThermalStatusListener} and Loading @@ -1915,14 +1913,8 @@ public final class PowerManager { */ public void addThermalStatusListener(@NonNull OnThermalStatusChangedListener listener) { Preconditions.checkNotNull(listener, "listener cannot be null"); synchronized (this) { if (mThermalService == null) { mThermalService = IThermalService.Stub.asInterface( ServiceManager.getService(Context.THERMAL_SERVICE)); } this.addThermalStatusListener(mContext.getMainExecutor(), listener); } } /** * This function adds a listener for thermal status change. Loading @@ -1934,11 +1926,6 @@ public final class PowerManager { @NonNull OnThermalStatusChangedListener listener) { Preconditions.checkNotNull(listener, "listener cannot be null"); Preconditions.checkNotNull(executor, "executor cannot be null"); synchronized (this) { if (mThermalService == null) { mThermalService = IThermalService.Stub.asInterface( ServiceManager.getService(Context.THERMAL_SERVICE)); } Preconditions.checkArgument(!mListenerMap.containsKey(listener), "Listener already registered: " + listener); IThermalStatusListener internalListener = new IThermalStatusListener.Stub() { Loading @@ -1964,7 +1951,6 @@ public final class PowerManager { throw e.rethrowFromSystemServer(); } } } /** * This function removes a listener for thermal status change Loading @@ -1973,11 +1959,6 @@ public final class PowerManager { */ public void removeThermalStatusListener(@NonNull OnThermalStatusChangedListener listener) { Preconditions.checkNotNull(listener, "listener cannot be null"); synchronized (this) { if (mThermalService == null) { mThermalService = IThermalService.Stub.asInterface( ServiceManager.getService(Context.THERMAL_SERVICE)); } IThermalStatusListener internalListener = mListenerMap.get(listener); Preconditions.checkArgument(internalListener != null, "Listener was not added"); try { Loading @@ -1990,6 +1971,61 @@ public final class PowerManager { throw e.rethrowFromSystemServer(); } } @CurrentTimeMillisLong private final AtomicLong mLastHeadroomUpdate = new AtomicLong(0L); private static final int MINIMUM_HEADROOM_TIME_MILLIS = 500; /** * Provides an estimate of how much thermal headroom the device currently has before hitting * severe throttling. * * Note that this only attempts to track the headroom of slow-moving sensors, such as the skin * temperature sensor. This means that there is no benefit to calling this function more * frequently than about once per second, and attempts to call significantly more frequently may * result in the function returning {@code NaN}. * * In addition, in order to be able to provide an accurate forecast, the system does not attempt * to forecast until it has multiple temperature samples from which to extrapolate. This should * only take a few seconds from the time of the first call, but during this time, no forecasting * will occur, and the current headroom will be returned regardless of the value of * {@code forecastSeconds}. * * The value returned is a non-negative float that represents how much of the thermal envelope * is in use (or is forecasted to be in use). A value of 1.0 indicates that the device is (or * will be) throttled at {@link #THERMAL_STATUS_SEVERE}. Such throttling can affect the CPU, * GPU, and other subsystems. Values may exceed 1.0, but there is no implied mapping to specific * thermal status levels beyond that point. This means that values greater than 1.0 may * correspond to {@link #THERMAL_STATUS_SEVERE}, but may also represent heavier throttling. * * A value of 0.0 corresponds to a fixed distance from 1.0, but does not correspond to any * particular thermal status or temperature. Values on (0.0, 1.0] may be expected to scale * linearly with temperature, though temperature changes over time are typically not linear. * Negative values will be clamped to 0.0 before returning. * * @param forecastSeconds how many seconds in the future to forecast. Given that device * conditions may change at any time, forecasts from further in the * future will likely be less accurate than forecasts in the near future. * @return a value greater than or equal to 0.0 where 1.0 indicates the SEVERE throttling * threshold, as described above. Returns NaN if the device does not support this * functionality or if this function is called significantly faster than once per * second. */ public float getThermalHeadroom(@IntRange(from = 0, to = 60) int forecastSeconds) { // Rate-limit calls into the thermal service long now = SystemClock.elapsedRealtime(); long timeSinceLastUpdate = now - mLastHeadroomUpdate.get(); if (timeSinceLastUpdate < MINIMUM_HEADROOM_TIME_MILLIS) { return Float.NaN; } try { float forecast = mThermalService.getThermalHeadroom(forecastSeconds); mLastHeadroomUpdate.set(SystemClock.elapsedRealtime()); return forecast; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** Loading core/tests/coretests/src/android/os/PowerManagerTest.java +22 −0 Original line number Diff line number Diff line Loading @@ -243,6 +243,28 @@ public class PowerManagerTest extends AndroidTestCase { .times(1)).onThermalStatusChanged(status); } @Test public void testGetThermalHeadroom() throws Exception { float headroom = mPm.getThermalHeadroom(0); // If the device doesn't support thermal headroom, return early if (Float.isNaN(headroom)) { return; } assertTrue("Expected non-negative headroom", headroom >= 0.0f); assertTrue("Expected reasonably small headroom", headroom < 10.0f); // Call again immediately to ensure rate limiting works headroom = mPm.getThermalHeadroom(0); assertTrue("Expected NaN because of rate limiting", Float.isNaN(headroom)); // Sleep for a second before attempting to call again so as to not get rate limited Thread.sleep(1000); headroom = mPm.getThermalHeadroom(5); assertFalse("Expected data to still be available", Float.isNaN(headroom)); assertTrue("Expected non-negative headroom", headroom >= 0.0f); assertTrue("Expected reasonably small headroom", headroom < 10.0f); } @Test public void testUserspaceRebootNotSupported_throwsUnsupportedOperationException() { // Can't use assumption framework with AndroidTestCase :( Loading Loading
api/current.txt +1 −0 Original line number Diff line number Diff line Loading @@ -36633,6 +36633,7 @@ package android.os { method public void addThermalStatusListener(@NonNull java.util.concurrent.Executor, @NonNull android.os.PowerManager.OnThermalStatusChangedListener); method public int getCurrentThermalStatus(); method public int getLocationPowerSaveMode(); method public float getThermalHeadroom(@IntRange(from=0, to=60) int); method public boolean isDeviceIdleMode(); method public boolean isIgnoringBatteryOptimizations(String); method public boolean isInteractive();
core/java/android/app/SystemServiceRegistry.java +7 −4 Original line number Diff line number Diff line Loading @@ -148,6 +148,7 @@ import android.os.IHardwarePropertiesManager; import android.os.IPowerManager; import android.os.IRecoverySystem; import android.os.ISystemUpdateManager; import android.os.IThermalService; import android.os.IUserManager; import android.os.IncidentManager; import android.os.PowerManager; Loading Loading @@ -576,10 +577,12 @@ public final class SystemServiceRegistry { new CachedServiceFetcher<PowerManager>() { @Override public PowerManager createService(ContextImpl ctx) throws ServiceNotFoundException { IBinder b = ServiceManager.getServiceOrThrow(Context.POWER_SERVICE); IPowerManager service = IPowerManager.Stub.asInterface(b); return new PowerManager(ctx.getOuterContext(), service, ctx.mMainThread.getHandler()); IBinder powerBinder = ServiceManager.getServiceOrThrow(Context.POWER_SERVICE); IPowerManager powerService = IPowerManager.Stub.asInterface(powerBinder); IBinder thermalBinder = ServiceManager.getServiceOrThrow(Context.THERMAL_SERVICE); IThermalService thermalService = IThermalService.Stub.asInterface(thermalBinder); return new PowerManager(ctx.getOuterContext(), powerService, thermalService, ctx.mMainThread.getHandler()); }}); registerService(Context.RECOVERY_SERVICE, RecoverySystem.class, Loading
core/java/android/os/IThermalService.aidl +7 −0 Original line number Diff line number Diff line Loading @@ -103,4 +103,11 @@ interface IThermalService { * {@hide} */ List<CoolingDevice> getCurrentCoolingDevicesWithType(in int type); /** * @param forecastSeconds how many seconds ahead to forecast the provided headroom * @return forecasted thermal headroom, normalized such that 1.0 indicates that throttling will * occur; returns NaN if the headroom or forecast is unavailable */ float getThermalHeadroom(int forecastSeconds); }
core/java/android/os/PowerManager.java +98 −62 Original line number Diff line number Diff line Loading @@ -18,7 +18,9 @@ package android.os; import android.Manifest.permission; import android.annotation.CallbackExecutor; import android.annotation.CurrentTimeMillisLong; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; Loading @@ -39,6 +41,7 @@ import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicLong; /** * This class gives you control of the power state of the device. Loading Loading @@ -916,20 +919,22 @@ public final class PowerManager { final IPowerManager mService; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) final Handler mHandler; final IThermalService mThermalService; /** We lazily initialize it.*/ private DeviceIdleManager mDeviceIdleManager; IThermalService mThermalService; private final ArrayMap<OnThermalStatusChangedListener, IThermalStatusListener> mListenerMap = new ArrayMap<>(); /** * {@hide} */ public PowerManager(Context context, IPowerManager service, Handler handler) { public PowerManager(Context context, IPowerManager service, IThermalService thermalService, Handler handler) { mContext = context; mService = service; mThermalService = thermalService; mHandler = handler; } Loading Loading @@ -1877,11 +1882,6 @@ public final class PowerManager { * thermal throttling. */ public @ThermalStatus int getCurrentThermalStatus() { synchronized (this) { if (mThermalService == null) { mThermalService = IThermalService.Stub.asInterface( ServiceManager.getService(Context.THERMAL_SERVICE)); } try { return mThermalService.getCurrentThermalStatus(); } catch (RemoteException e) { Loading @@ -1889,8 +1889,6 @@ public final class PowerManager { } } } /** * Listener passed to * {@link PowerManager#addThermalStatusListener} and Loading @@ -1915,14 +1913,8 @@ public final class PowerManager { */ public void addThermalStatusListener(@NonNull OnThermalStatusChangedListener listener) { Preconditions.checkNotNull(listener, "listener cannot be null"); synchronized (this) { if (mThermalService == null) { mThermalService = IThermalService.Stub.asInterface( ServiceManager.getService(Context.THERMAL_SERVICE)); } this.addThermalStatusListener(mContext.getMainExecutor(), listener); } } /** * This function adds a listener for thermal status change. Loading @@ -1934,11 +1926,6 @@ public final class PowerManager { @NonNull OnThermalStatusChangedListener listener) { Preconditions.checkNotNull(listener, "listener cannot be null"); Preconditions.checkNotNull(executor, "executor cannot be null"); synchronized (this) { if (mThermalService == null) { mThermalService = IThermalService.Stub.asInterface( ServiceManager.getService(Context.THERMAL_SERVICE)); } Preconditions.checkArgument(!mListenerMap.containsKey(listener), "Listener already registered: " + listener); IThermalStatusListener internalListener = new IThermalStatusListener.Stub() { Loading @@ -1964,7 +1951,6 @@ public final class PowerManager { throw e.rethrowFromSystemServer(); } } } /** * This function removes a listener for thermal status change Loading @@ -1973,11 +1959,6 @@ public final class PowerManager { */ public void removeThermalStatusListener(@NonNull OnThermalStatusChangedListener listener) { Preconditions.checkNotNull(listener, "listener cannot be null"); synchronized (this) { if (mThermalService == null) { mThermalService = IThermalService.Stub.asInterface( ServiceManager.getService(Context.THERMAL_SERVICE)); } IThermalStatusListener internalListener = mListenerMap.get(listener); Preconditions.checkArgument(internalListener != null, "Listener was not added"); try { Loading @@ -1990,6 +1971,61 @@ public final class PowerManager { throw e.rethrowFromSystemServer(); } } @CurrentTimeMillisLong private final AtomicLong mLastHeadroomUpdate = new AtomicLong(0L); private static final int MINIMUM_HEADROOM_TIME_MILLIS = 500; /** * Provides an estimate of how much thermal headroom the device currently has before hitting * severe throttling. * * Note that this only attempts to track the headroom of slow-moving sensors, such as the skin * temperature sensor. This means that there is no benefit to calling this function more * frequently than about once per second, and attempts to call significantly more frequently may * result in the function returning {@code NaN}. * * In addition, in order to be able to provide an accurate forecast, the system does not attempt * to forecast until it has multiple temperature samples from which to extrapolate. This should * only take a few seconds from the time of the first call, but during this time, no forecasting * will occur, and the current headroom will be returned regardless of the value of * {@code forecastSeconds}. * * The value returned is a non-negative float that represents how much of the thermal envelope * is in use (or is forecasted to be in use). A value of 1.0 indicates that the device is (or * will be) throttled at {@link #THERMAL_STATUS_SEVERE}. Such throttling can affect the CPU, * GPU, and other subsystems. Values may exceed 1.0, but there is no implied mapping to specific * thermal status levels beyond that point. This means that values greater than 1.0 may * correspond to {@link #THERMAL_STATUS_SEVERE}, but may also represent heavier throttling. * * A value of 0.0 corresponds to a fixed distance from 1.0, but does not correspond to any * particular thermal status or temperature. Values on (0.0, 1.0] may be expected to scale * linearly with temperature, though temperature changes over time are typically not linear. * Negative values will be clamped to 0.0 before returning. * * @param forecastSeconds how many seconds in the future to forecast. Given that device * conditions may change at any time, forecasts from further in the * future will likely be less accurate than forecasts in the near future. * @return a value greater than or equal to 0.0 where 1.0 indicates the SEVERE throttling * threshold, as described above. Returns NaN if the device does not support this * functionality or if this function is called significantly faster than once per * second. */ public float getThermalHeadroom(@IntRange(from = 0, to = 60) int forecastSeconds) { // Rate-limit calls into the thermal service long now = SystemClock.elapsedRealtime(); long timeSinceLastUpdate = now - mLastHeadroomUpdate.get(); if (timeSinceLastUpdate < MINIMUM_HEADROOM_TIME_MILLIS) { return Float.NaN; } try { float forecast = mThermalService.getThermalHeadroom(forecastSeconds); mLastHeadroomUpdate.set(SystemClock.elapsedRealtime()); return forecast; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** Loading
core/tests/coretests/src/android/os/PowerManagerTest.java +22 −0 Original line number Diff line number Diff line Loading @@ -243,6 +243,28 @@ public class PowerManagerTest extends AndroidTestCase { .times(1)).onThermalStatusChanged(status); } @Test public void testGetThermalHeadroom() throws Exception { float headroom = mPm.getThermalHeadroom(0); // If the device doesn't support thermal headroom, return early if (Float.isNaN(headroom)) { return; } assertTrue("Expected non-negative headroom", headroom >= 0.0f); assertTrue("Expected reasonably small headroom", headroom < 10.0f); // Call again immediately to ensure rate limiting works headroom = mPm.getThermalHeadroom(0); assertTrue("Expected NaN because of rate limiting", Float.isNaN(headroom)); // Sleep for a second before attempting to call again so as to not get rate limited Thread.sleep(1000); headroom = mPm.getThermalHeadroom(5); assertFalse("Expected data to still be available", Float.isNaN(headroom)); assertTrue("Expected non-negative headroom", headroom >= 0.0f); assertTrue("Expected reasonably small headroom", headroom < 10.0f); } @Test public void testUserspaceRebootNotSupported_throwsUnsupportedOperationException() { // Can't use assumption framework with AndroidTestCase :( Loading