Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b0470583 authored by Dan Stoza's avatar Dan Stoza
Browse files

Add PowerManager#getThermalHeadroom

Adds getThermalHeadroom, which allows users to determine how close the
device currently is to SEVERE thermal throttling. For intensive
applications which can scale their workloads, this allows them to help
manage the thermal envelope of the device.

Test: Manual, modify Filament test app to call and observe values
Test: PowerManagerTest#testGetThermalHeadroom
Test: atest android.os.cts.PowerManager_ThermalTest
Test: atest com.android.systemui.statusbar.phone.StatusBarTest
Test: atest CachedDeviceStateServiceTest
Test: atest AbstractAccessibilityServiceConnectionTest
Test: atest KeyEventDispatcherTest
Test: atest AttentionManagerServiceTest
Test: atest HdmiControlServiceTest
Test: atest ThermalManagerServiceTest
Test: atest RecoverySystemServiceTest
Bug: 136285293
Change-Id: I26e1c849c4b08cbf7ee0057f555cf0893750da43
parent 75088d14
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -36625,6 +36625,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();
+7 −4
Original line number Diff line number Diff line
@@ -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;
@@ -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,
+7 −0
Original line number Diff line number Diff line
@@ -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);
}
+98 −62
Original line number Diff line number Diff line
@@ -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;
@@ -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.
@@ -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;
    }

@@ -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) {
@@ -1889,8 +1889,6 @@ public final class PowerManager {
        }
    }

    }

    /**
     * Listener passed to
     * {@link PowerManager#addThermalStatusListener} and
@@ -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.
@@ -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() {
@@ -1964,7 +1951,6 @@ public final class PowerManager {
            throw e.rethrowFromSystemServer();
        }
    }
    }

    /**
     * This function removes a listener for thermal status change
@@ -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 {
@@ -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();
        }
    }

    /**
+22 −0
Original line number Diff line number Diff line
@@ -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