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

Commit a6781c83 authored by Dan Stoza's avatar Dan Stoza Committed by Android (Google) Code Review
Browse files

Merge "Add PowerManager#getThermalHeadroom"

parents 91619830 b0470583
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -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();
+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