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

Commit 85d3dbf4 authored by Xiang Wang's avatar Xiang Wang Committed by Android (Google) Code Review
Browse files

Merge "Add SDK support for thermal headroom callback API" into main

parents 5ef6e007 2b05463e
Loading
Loading
Loading
Loading
+8 −1
Original line number Diff line number Diff line
@@ -33979,12 +33979,14 @@ package android.os {
  }
  public final class PowerManager {
    method @FlaggedApi("android.os.allow_thermal_thresholds_callback") public void addThermalHeadroomListener(@NonNull android.os.PowerManager.OnThermalHeadroomChangedListener);
    method @FlaggedApi("android.os.allow_thermal_thresholds_callback") public void addThermalHeadroomListener(@NonNull java.util.concurrent.Executor, @NonNull android.os.PowerManager.OnThermalHeadroomChangedListener);
    method public void addThermalStatusListener(@NonNull android.os.PowerManager.OnThermalStatusChangedListener);
    method public void addThermalStatusListener(@NonNull java.util.concurrent.Executor, @NonNull android.os.PowerManager.OnThermalStatusChangedListener);
    method @Nullable public java.time.Duration getBatteryDischargePrediction();
    method public int getCurrentThermalStatus();
    method public int getLocationPowerSaveMode();
    method public float getThermalHeadroom(@IntRange(from=0, to=60) int);
    method @FloatRange(from=0.0f) 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);
@@ -34002,6 +34004,7 @@ package android.os {
    method public boolean isWakeLockLevelSupported(int);
    method public android.os.PowerManager.WakeLock newWakeLock(int, String);
    method @RequiresPermission(android.Manifest.permission.REBOOT) public void reboot(@Nullable String);
    method @FlaggedApi("android.os.allow_thermal_thresholds_callback") public void removeThermalHeadroomListener(@NonNull android.os.PowerManager.OnThermalHeadroomChangedListener);
    method public void removeThermalStatusListener(@NonNull android.os.PowerManager.OnThermalStatusChangedListener);
    field @Deprecated @RequiresPermission(value=android.Manifest.permission.TURN_SCREEN_ON, conditional=true) public static final int ACQUIRE_CAUSES_WAKEUP = 268435456; // 0x10000000
    field public static final String ACTION_DEVICE_IDLE_MODE_CHANGED = "android.os.action.DEVICE_IDLE_MODE_CHANGED";
@@ -34034,6 +34037,10 @@ package android.os {
    field public static final int THERMAL_STATUS_SHUTDOWN = 6; // 0x6
  }
  @FlaggedApi("android.os.allow_thermal_thresholds_callback") public static interface PowerManager.OnThermalHeadroomChangedListener {
    method public void onThermalHeadroomChanged(@FloatRange(from=0.0f) float, @FloatRange(from=0.0f) float, @IntRange(from=0) int, @NonNull java.util.Map<java.lang.Integer,java.lang.Float>);
  }
  public static interface PowerManager.OnThermalStatusChangedListener {
    method public void onThermalStatusChanged(int);
  }
+1 −0
Original line number Diff line number Diff line
@@ -206,6 +206,7 @@ filegroup {
        "android/os/Temperature.aidl",
        "android/os/CoolingDevice.aidl",
        "android/os/IThermalEventListener.aidl",
        "android/os/IThermalHeadroomListener.aidl",
        "android/os/IThermalStatusListener.aidl",
        "android/os/IThermalService.aidl",
        "android/os/IPowerManager.aidl",
+31 −0
Original line number Diff line number Diff line
/*
** Copyright 2024, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
**     http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/

package android.os;

/**
 * Listener for thermal headroom and threshold changes.
 * This is mainly used by {@link android.os.PowerManager} to serve public thermal headoom related
 * APIs.
 * {@hide}
 */
oneway interface IThermalHeadroomListener {
    /**
     * Called when thermal headroom or thresholds changed.
     */
    void onHeadroomChange(in float headroom, in float forecastHeadroom,
                                 in int forecastSeconds, in float[] thresholds);
}
+17 −0
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package android.os;

import android.os.CoolingDevice;
import android.os.IThermalEventListener;
import android.os.IThermalHeadroomListener;
import android.os.IThermalStatusListener;
import android.os.Temperature;

@@ -116,4 +117,20 @@ interface IThermalService {
     * @return thermal headroom for each thermal status
     */
    float[] getThermalHeadroomThresholds();

    /**
      * Register a listener for thermal headroom change.
      * @param listener the {@link android.os.IThermalHeadroomListener} to be notified.
      * @return true if registered successfully.
      * {@hide}
      */
    boolean registerThermalHeadroomListener(in IThermalHeadroomListener listener);

    /**
      * Unregister a previously-registered listener for thermal headroom.
      * @param listener the {@link android.os.IThermalHeadroomListener} to no longer be notified.
      * @return true if unregistered successfully.
      * {@hide}
      */
    boolean unregisterThermalHeadroomListener(in IThermalHeadroomListener listener);
}
+185 −53
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import android.Manifest.permission;
import android.annotation.CallbackExecutor;
import android.annotation.CurrentTimeMillisLong;
import android.annotation.FlaggedApi;
import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -40,6 +41,7 @@ import android.util.Log;
import android.util.proto.ProtoOutputStream;
import android.view.Display;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;

import java.lang.annotation.ElementType;
@@ -1199,10 +1201,12 @@ public final class PowerManager {
    /** We lazily initialize it.*/
    private PowerExemptionManager mPowerExemptionManager;

    @GuardedBy("mThermalStatusListenerMap")
    private final ArrayMap<OnThermalStatusChangedListener, IThermalStatusListener>
            mListenerMap = new ArrayMap<>();
    private final Object mThermalHeadroomThresholdsLock = new Object();
    private float[] mThermalHeadroomThresholds = null;
            mThermalStatusListenerMap = new ArrayMap<>();
    @GuardedBy("mThermalHeadroomListenerMap")
    private final ArrayMap<OnThermalHeadroomChangedListener, IThermalHeadroomListener>
            mThermalHeadroomListenerMap = new ArrayMap<>();

    /**
     * {@hide}
@@ -2689,15 +2693,59 @@ public final class PowerManager {
        void onThermalStatusChanged(@ThermalStatus int status);
    }

    /**
     * Listener passed to
     * {@link PowerManager#addThermalHeadroomListener} and
     * {@link PowerManager#removeThermalHeadroomListener}
     * to notify caller of Thermal headroom or thresholds changes.
     */
    @FlaggedApi(Flags.FLAG_ALLOW_THERMAL_THRESHOLDS_CALLBACK)
    public interface OnThermalHeadroomChangedListener {

        /**
     * This function adds a listener for thermal status change, listen call back will be
         * Called when overall thermal headroom or headroom thresholds have significantly
         * changed that requires action.
         * <p>
         * This may not be used to fully replace the {@link #getThermalHeadroom(int)} API as it will
         * only notify on one of the conditions below that will significantly change one or both
         * values of current headroom and headroom thresholds since previous callback:
         *   1. thermal throttling events: when the skin temperature has cross any of the thresholds
         *      and there isn't a previous callback in a short time ago with similar values.
         *   2. skin temperature threshold change events: note that if the absolute °C threshold
         *      values change in a way that does not significantly change the current headroom nor
         *      headroom thresholds, it will not trigger any callback. The client should not
         *      need to take action in such case since the difference from temperature vs threshold
         *      hasn't changed.
         * <p>
         * By API version 36, it provides a forecast in the same call for developer's convenience
         * based on a {@code forecastSeconds} defined by the device, which can be static or dynamic
         * varied by OEM. Be aware that it will not notify on forecast temperature change but the
         * events mentioned above. So periodically polling against {@link #getThermalHeadroom(int)}
         * API should still be used to actively monitor temperature forecast in advance.
         * <p>
         * This serves as a more advanced option compared to thermal status listener, where the
         * latter will only notify on thermal throttling events with status update.
         *
         * @param headroom current headroom
         * @param forecastHeadroom forecasted headroom in future
         * @param forecastSeconds how many seconds in the future used in forecast
         * @param thresholds new headroom thresholds, see {@link #getThermalHeadroomThresholds()}
         */
        void onThermalHeadroomChanged(
                @FloatRange(from = 0f) float headroom,
                @FloatRange(from = 0f) float forecastHeadroom,
                @IntRange(from = 0) int forecastSeconds,
                @NonNull Map<@ThermalStatus Integer, Float> thresholds);
    }

    /**
     * This function adds a listener for thermal status change, listener callback will be
     * enqueued tasks on the main thread
     *
     * @param listener listener to be added,
     */
    public void addThermalStatusListener(@NonNull OnThermalStatusChangedListener listener) {
        Objects.requireNonNull(listener, "listener cannot be null");
        Objects.requireNonNull(listener, "Thermal status listener cannot be null");
        addThermalStatusListener(mContext.getMainExecutor(), listener);
    }

@@ -2709,10 +2757,11 @@ public final class PowerManager {
     */
    public void addThermalStatusListener(@NonNull @CallbackExecutor Executor executor,
            @NonNull OnThermalStatusChangedListener listener) {
        Objects.requireNonNull(listener, "listener cannot be null");
        Objects.requireNonNull(executor, "executor cannot be null");
        Preconditions.checkArgument(!mListenerMap.containsKey(listener),
                "Listener already registered: %s", listener);
        Objects.requireNonNull(listener, "Thermal status listener cannot be null");
        Objects.requireNonNull(executor, "Executor cannot be null");
        synchronized (mThermalStatusListenerMap) {
            Preconditions.checkArgument(!mThermalStatusListenerMap.containsKey(listener),
                    "Thermal status listener already registered: %s", listener);
            IThermalStatusListener internalListener = new IThermalStatusListener.Stub() {
                @Override
                public void onStatusChange(int status) {
@@ -2726,14 +2775,15 @@ public final class PowerManager {
            };
            try {
                if (mThermalService.registerThermalStatusListener(internalListener)) {
                mListenerMap.put(listener, internalListener);
                    mThermalStatusListenerMap.put(listener, internalListener);
                } else {
                throw new RuntimeException("Listener failed to set");
                    throw new RuntimeException("Thermal status listener failed to set");
                }
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
    }

    /**
     * This function removes a listener for thermal status change
@@ -2741,19 +2791,100 @@ public final class PowerManager {
     * @param listener listener to be removed
     */
    public void removeThermalStatusListener(@NonNull OnThermalStatusChangedListener listener) {
        Objects.requireNonNull(listener, "listener cannot be null");
        IThermalStatusListener internalListener = mListenerMap.get(listener);
        Preconditions.checkArgument(internalListener != null, "Listener was not added");
        Objects.requireNonNull(listener, "Thermal status listener cannot be null");
        synchronized (mThermalStatusListenerMap) {
            IThermalStatusListener internalListener = mThermalStatusListenerMap.get(listener);
            Preconditions.checkArgument(internalListener != null,
                    "Thermal status listener was not added");
            try {
                if (mThermalService.unregisterThermalStatusListener(internalListener)) {
                mListenerMap.remove(listener);
                    mThermalStatusListenerMap.remove(listener);
                } else {
                throw new RuntimeException("Listener failed to remove");
                    throw new RuntimeException("Failed to unregister thermal status listener");
                }
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
    }

    /**
     * This function adds a listener for thermal headroom change, listener callback will be
     * enqueued tasks on the main thread
     *
     * @param listener listener to be added,
     */
    @FlaggedApi(Flags.FLAG_ALLOW_THERMAL_THRESHOLDS_CALLBACK)
    public void addThermalHeadroomListener(@NonNull OnThermalHeadroomChangedListener listener) {
        Objects.requireNonNull(listener, "Thermal headroom listener cannot be null");
        addThermalHeadroomListener(mContext.getMainExecutor(), listener);
    }

    /**
     * This function adds a listener for thermal headroom change.
     *
     * @param executor {@link Executor} to handle listener callback.
     * @param listener listener to be added.
     */
    @FlaggedApi(Flags.FLAG_ALLOW_THERMAL_THRESHOLDS_CALLBACK)
    public void addThermalHeadroomListener(@NonNull @CallbackExecutor Executor executor,
            @NonNull OnThermalHeadroomChangedListener listener) {
        Objects.requireNonNull(listener, "Thermal headroom listener cannot be null");
        Objects.requireNonNull(executor, "Executor cannot be null");
        synchronized (mThermalHeadroomListenerMap) {
            Preconditions.checkArgument(!mThermalHeadroomListenerMap.containsKey(listener),
                    "Thermal headroom listener already registered: %s", listener);
            IThermalHeadroomListener internalListener = new IThermalHeadroomListener.Stub() {
                @Override
                public void onHeadroomChange(float headroom, float forecastHeadroom,
                        int forecastSeconds, float[] thresholds)
                        throws RemoteException {
                    final Map<Integer, Float> map = convertThresholdsToMap(thresholds);
                    final long token = Binder.clearCallingIdentity();
                    try {
                        executor.execute(() -> listener.onThermalHeadroomChanged(headroom,
                                forecastHeadroom, forecastSeconds, map));
                    } finally {
                        Binder.restoreCallingIdentity(token);
                    }
                }
            };
            try {
                if (mThermalService.registerThermalHeadroomListener(internalListener)) {
                    mThermalHeadroomListenerMap.put(listener, internalListener);
                } else {
                    throw new RuntimeException("Thermal headroom listener failed to set");
                }
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
    }

    /**
     * This function removes a listener for Thermal headroom change
     *
     * @param listener listener to be removed
     */
    @FlaggedApi(Flags.FLAG_ALLOW_THERMAL_THRESHOLDS_CALLBACK)
    public void removeThermalHeadroomListener(@NonNull OnThermalHeadroomChangedListener listener) {
        Objects.requireNonNull(listener, "Thermal headroom listener cannot be null");
        synchronized (mThermalHeadroomListenerMap) {
            IThermalHeadroomListener internalListener = mThermalHeadroomListenerMap.get(listener);
            Preconditions.checkArgument(internalListener != null,
                    "Thermal headroom listener was not added");
            try {
                if (mThermalService.unregisterThermalHeadroomListener(internalListener)) {
                    mThermalHeadroomListenerMap.remove(listener);
                } else {
                    throw new RuntimeException("Failed to unregister thermal status listener");
                }
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
    }


    @CurrentTimeMillisLong
    private final AtomicLong mLastHeadroomUpdate = new AtomicLong(0L);
@@ -2794,7 +2925,8 @@ public final class PowerManager {
     *         functionality or if this function is called significantly faster than once per
     *         second.
     */
    public float getThermalHeadroom(@IntRange(from = 0, to = 60) int forecastSeconds) {
    public @FloatRange(from = 0f) 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();
@@ -2839,9 +2971,11 @@ public final class PowerManager {
     * 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.
     * Starting at {@link android.os.Build.VERSION_CODES#BAKLAVA} the returned map of thresholds can
     * change between calls to this function, one could use the new
     * {@link #addThermalHeadroomListener(Executor, OnThermalHeadroomChangedListener)} API to
     * register a listener and get callback for changes to thresholds.
     * <p>
     *
     * @return map from each thermal status to its thermal headroom
     * @throws IllegalStateException if the thermal service is not ready
@@ -2850,23 +2984,21 @@ public final class PowerManager {
    @FlaggedApi(Flags.FLAG_ALLOW_THERMAL_HEADROOM_THRESHOLDS)
    public @NonNull Map<@ThermalStatus Integer, Float> getThermalHeadroomThresholds() {
        try {
            synchronized (mThermalHeadroomThresholdsLock) {
                if (mThermalHeadroomThresholds == null) {
                    mThermalHeadroomThresholds = mThermalService.getThermalHeadroomThresholds();
            return convertThresholdsToMap(mThermalService.getThermalHeadroomThresholds());
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    private Map<@ThermalStatus Integer, Float> convertThresholdsToMap(final float[] thresholds) {
        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]);
        for (int status = THERMAL_STATUS_LIGHT; status <= THERMAL_STATUS_SHUTDOWN; status++) {
            if (!Float.isNaN(thresholds[status])) {
                ret.put(status, thresholds[status]);
            }
        }
        return ret;
    }
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * If true, the doze component is not started until after the screen has been
Loading