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

Commit 7ab922b3 authored by Xiang Wang's avatar Xiang Wang
Browse files

Add getThermalHeadroomThresholds API

Test: atest ThermalManagerServiceTest
Bug: 288119641
Change-Id: I06b1f389dfb6805c7aa07599b888438a2eb17993
parent 87256704
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -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();
+5 −0
Original line number Diff line number Diff line
@@ -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();
}
+64 −0
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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}
@@ -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,
@@ -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.
+7 −0
Original line number Diff line number Diff line
@@ -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"
+70 −15
Original line number Diff line number Diff line
@@ -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;
@@ -181,7 +182,7 @@ public class ThermalManagerService extends SystemService {
                onTemperatureChanged(temperatures.get(i), false);
            }
            onTemperatureMapChangedLocked();
            mTemperatureWatcher.updateSevereThresholds();
            mTemperatureWatcher.updateThresholds();
            mHalReady.set(true);
        }
    }
@@ -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);
@@ -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);
        }
@@ -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();
                    }
                }
@@ -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;

@@ -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);
                    }
                }
            }
@@ -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;
@@ -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