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

Commit 87546f50 authored by Daniel Solomon's avatar Daniel Solomon
Browse files

Add display brightness throttler

This change adds BrightnessThrottler, which is responsible for limiting
the usable range of display brightness based on skin temperature thermal
throttling (extensible to account for other events/conditions in the
future). Brightness constraints calculated by BrightnessThrottling are
applied by DisplayPowerController as a brightness transform similarly to
dimming and low power states.

This change also adds a field to BrightnessInfo in order to capture the
reason for an unusual max brightness (currently only accounts for a
thermal reason).

Finally, change HighBrightnessModeController (HBMC) so that HBM thermal
throttling is still reported correctly to FrameworkStatsLog when
brightness throttling is performed in DisplayPowerController (through
BrightnessThrottler) rather than through HBMC's own thermal throttling
mechanism. HBMC's own thermal throttling mechanism will be deprecated in
a future change.

Bug: 206857086
Bug: 212634465
Test: atest BrightnessThrottlerTest DisplayModeDirectorTest
    BrightnessLevelPreferenceControllerTest
    HighBrightnessModeControllerTest
Test: Manually trigger and clear thermal throttling, and verify
    transitions,
    1. While in manual brightness mode
    2. While in automatic Brightness mode
    3. While high brightness mode is enabled for HDR
    3. Across display suspend/resume events
Change-Id: I4c10f037a0a616e84fc109cc755bf3a5eaa3d111
parent 221ed8b5
Loading
Loading
Loading
Loading
+43 −3
Original line number Diff line number Diff line
@@ -57,6 +57,23 @@ public final class BrightnessInfo implements Parcelable {
     */
    public static final int HIGH_BRIGHTNESS_MODE_HDR = 2;

    @IntDef(prefix = {"BRIGHTNESS_MAX_REASON_"}, value = {
            BRIGHTNESS_MAX_REASON_NONE,
            BRIGHTNESS_MAX_REASON_THERMAL
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface BrightnessMaxReason {}

    /**
     * Maximum brightness is unrestricted.
     */
    public static final int BRIGHTNESS_MAX_REASON_NONE = 0;

    /**
     * Maximum brightness is restricted due to thermal throttling.
     */
    public static final int BRIGHTNESS_MAX_REASON_THERMAL = 1;

    /** Brightness */
    public final float brightness;

@@ -78,21 +95,29 @@ public final class BrightnessInfo implements Parcelable {
     */
    public final int highBrightnessMode;

    /**
     * The current reason for restricting maximum brightness.
     * Can be any of BRIGHTNESS_MAX_REASON_* values.
     */
    public final int brightnessMaxReason;

    public BrightnessInfo(float brightness, float brightnessMinimum, float brightnessMaximum,
            @HighBrightnessMode int highBrightnessMode, float highBrightnessTransitionPoint) {
            @HighBrightnessMode int highBrightnessMode, float highBrightnessTransitionPoint,
            @BrightnessMaxReason int brightnessMaxReason) {
        this(brightness, brightness, brightnessMinimum, brightnessMaximum, highBrightnessMode,
                highBrightnessTransitionPoint);
                highBrightnessTransitionPoint, brightnessMaxReason);
    }

    public BrightnessInfo(float brightness, float adjustedBrightness, float brightnessMinimum,
            float brightnessMaximum, @HighBrightnessMode int highBrightnessMode,
            float highBrightnessTransitionPoint) {
            float highBrightnessTransitionPoint, @BrightnessMaxReason int brightnessMaxReason) {
        this.brightness = brightness;
        this.adjustedBrightness = adjustedBrightness;
        this.brightnessMinimum = brightnessMinimum;
        this.brightnessMaximum = brightnessMaximum;
        this.highBrightnessMode = highBrightnessMode;
        this.highBrightnessTransitionPoint = highBrightnessTransitionPoint;
        this.brightnessMaxReason =  brightnessMaxReason;
    }

    /**
@@ -110,6 +135,19 @@ public final class BrightnessInfo implements Parcelable {
        return "invalid";
    }

    /**
     * @return User-friendly string for specified {@link BrightnessMaxReason} parameter.
     */
    public static String briMaxReasonToString(@BrightnessMaxReason int reason) {
        switch (reason) {
            case BRIGHTNESS_MAX_REASON_NONE:
                return "none";
            case BRIGHTNESS_MAX_REASON_THERMAL:
                return "thermal";
        }
        return "invalid";
    }

    @Override
    public int describeContents() {
        return 0;
@@ -123,6 +161,7 @@ public final class BrightnessInfo implements Parcelable {
        dest.writeFloat(brightnessMaximum);
        dest.writeInt(highBrightnessMode);
        dest.writeFloat(highBrightnessTransitionPoint);
        dest.writeInt(brightnessMaxReason);
    }

    public static final @android.annotation.NonNull Creator<BrightnessInfo> CREATOR =
@@ -145,6 +184,7 @@ public final class BrightnessInfo implements Parcelable {
        brightnessMaximum = source.readFloat();
        highBrightnessMode = source.readInt();
        highBrightnessTransitionPoint = source.readFloat();
        brightnessMaxReason = source.readInt();
    }

}
+262 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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 com.android.server.display;

import android.content.Context;
import android.hardware.display.BrightnessInfo;
import android.os.Handler;
import android.os.IThermalEventListener;
import android.os.IThermalService;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.Temperature;
import android.util.Slog;

import com.android.server.display.DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel;
import com.android.server.display.DisplayDeviceConfig.BrightnessThrottlingData;

import java.io.PrintWriter;

/**
 * This class monitors various conditions, such as skin temperature throttling status, and limits
 * the allowed brightness range accordingly.
 */
class BrightnessThrottler {
    private static final String TAG = "BrightnessThrottler";
    private static final boolean DEBUG = false;

    private static final int THROTTLING_INVALID = -1;

    private final Injector mInjector;
    private final Handler mHandler;
    private BrightnessThrottlingData mThrottlingData;
    private final Runnable mThrottlingChangeCallback;
    private final SkinThermalStatusObserver mSkinThermalStatusObserver;
    private int mThrottlingStatus;
    private float mBrightnessCap = PowerManager.BRIGHTNESS_MAX;
    private @BrightnessInfo.BrightnessMaxReason int mBrightnessMaxReason =
        BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;

    BrightnessThrottler(Handler handler, BrightnessThrottlingData throttlingData,
            Runnable throttlingChangeCallback) {
        this(new Injector(), handler, throttlingData, throttlingChangeCallback);
    }

    BrightnessThrottler(Injector injector, Handler handler, BrightnessThrottlingData throttlingData,
            Runnable throttlingChangeCallback) {
        mInjector = injector;
        mHandler = handler;
        mThrottlingData = throttlingData;
        mThrottlingChangeCallback = throttlingChangeCallback;
        mSkinThermalStatusObserver = new SkinThermalStatusObserver(mInjector, mHandler);

        resetThrottlingData(mThrottlingData);
    }

    boolean deviceSupportsThrottling() {
        return mThrottlingData != null;
    }

    float getBrightnessCap() {
        return mBrightnessCap;
    }

    int getBrightnessMaxReason() {
        return mBrightnessMaxReason;
    }

    boolean isThrottled() {
        return mBrightnessMaxReason != BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
    }

    void stop() {
        mSkinThermalStatusObserver.stopObserving();

        // We're asked to stop throttling, so reset brightness restrictions.
        mBrightnessCap = PowerManager.BRIGHTNESS_MAX;
        mBrightnessMaxReason = BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;

        // We set throttling status to an invalid value here so that we act on the first throttling
        // value received from the thermal service after registration, even if that throttling value
        // is THROTTLING_NONE.
        mThrottlingStatus = THROTTLING_INVALID;
    }

    void resetThrottlingData(BrightnessThrottlingData throttlingData) {
        stop();
        mThrottlingData = throttlingData;

        if (deviceSupportsThrottling()) {
            mSkinThermalStatusObserver.startObserving();
        }
    }

    private float verifyAndConstrainBrightnessCap(float brightness) {
        if (brightness < PowerManager.BRIGHTNESS_MIN) {
            Slog.e(TAG, "brightness " + brightness + " is lower than the minimum possible "
                    + "brightness " + PowerManager.BRIGHTNESS_MIN);
            brightness = PowerManager.BRIGHTNESS_MIN;
        }

        if (brightness > PowerManager.BRIGHTNESS_MAX) {
            Slog.e(TAG, "brightness " + brightness + " is higher than the maximum possible "
                    + "brightness " + PowerManager.BRIGHTNESS_MAX);
            brightness = PowerManager.BRIGHTNESS_MAX;
        }

        return brightness;
    }

    private void thermalStatusChanged(@Temperature.ThrottlingStatus int newStatus) {
        if (mThrottlingStatus != newStatus) {
            mThrottlingStatus = newStatus;
            updateThrottling();
        }
    }

    private void updateThrottling() {
        if (!deviceSupportsThrottling()) {
            return;
        }

        float brightnessCap = PowerManager.BRIGHTNESS_MAX;
        int brightnessMaxReason = BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;

        if (mThrottlingStatus != THROTTLING_INVALID) {
            // Throttling levels are sorted by increasing severity
            for (ThrottlingLevel level : mThrottlingData.throttlingLevels) {
                if (level.thermalStatus <= mThrottlingStatus) {
                    brightnessCap = level.brightness;
                    brightnessMaxReason = BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL;
                } else {
                    // Throttling levels that are greater than the current status are irrelevant
                    break;
                }
            }
        }

        if (mBrightnessCap != brightnessCap || mBrightnessMaxReason != brightnessMaxReason) {
            mBrightnessCap = verifyAndConstrainBrightnessCap(brightnessCap);
            mBrightnessMaxReason = brightnessMaxReason;

            if (DEBUG) {
                Slog.d(TAG, "State changed: mBrightnessCap = " + mBrightnessCap
                        + ", mBrightnessMaxReason = "
                        + BrightnessInfo.briMaxReasonToString(mBrightnessMaxReason));
            }

            if (mThrottlingChangeCallback != null) {
                mThrottlingChangeCallback.run();
            }
        }
    }

    void dump(PrintWriter pw) {
        mHandler.runWithScissors(() -> dumpLocal(pw), 1000);
    }

    private void dumpLocal(PrintWriter pw) {
        pw.println("BrightnessThrottler:");
        pw.println("  mThrottlingData=" + mThrottlingData);
        pw.println("  mThrottlingStatus=" + mThrottlingStatus);
        pw.println("  mBrightnessCap=" + mBrightnessCap);
        pw.println("  mBrightnessMaxReason=" +
            BrightnessInfo.briMaxReasonToString(mBrightnessMaxReason));

        mSkinThermalStatusObserver.dump(pw);
    }

    private final class SkinThermalStatusObserver extends IThermalEventListener.Stub {
        private final Injector mInjector;
        private final Handler mHandler;

        private IThermalService mThermalService;
        private boolean mStarted;

        SkinThermalStatusObserver(Injector injector, Handler handler) {
            mInjector = injector;
            mHandler = handler;
        }

        @Override
        public void notifyThrottling(Temperature temp) {
            if (DEBUG) {
                Slog.d(TAG, "New thermal throttling status = " + temp.getStatus());
            }
            mHandler.post(() -> {
                final @Temperature.ThrottlingStatus int status = temp.getStatus();
                thermalStatusChanged(status);
            });
        }

        void startObserving() {
            if (mStarted) {
                if (DEBUG) {
                    Slog.d(TAG, "Thermal status observer already started");
                }
                return;
            }
            mThermalService = mInjector.getThermalService();
            if (mThermalService == null) {
                Slog.e(TAG, "Could not observe thermal status. Service not available");
                return;
            }
            try {
                // We get a callback immediately upon registering so there's no need to query
                // for the current value.
                mThermalService.registerThermalEventListenerWithType(this, Temperature.TYPE_SKIN);
                mStarted = true;
            } catch (RemoteException e) {
                Slog.e(TAG, "Failed to register thermal status listener", e);
            }
        }

        void stopObserving() {
            if (!mStarted) {
                if (DEBUG) {
                    Slog.d(TAG, "Stop skipped because thermal status observer not started");
                }
                return;
            }
            try {
                mThermalService.unregisterThermalEventListener(this);
                mStarted = false;
            } catch (RemoteException e) {
                Slog.e(TAG, "Failed to unregister thermal status listener", e);
            }
            mThermalService = null;
        }

        void dump(PrintWriter writer) {
            writer.println("  SkinThermalStatusObserver:");
            writer.println("    mStarted: " + mStarted);
            if (mThermalService != null) {
                writer.println("    ThermalService available");
            } else {
                writer.println("    ThermalService not available");
            }
        }
    }

    public static class Injector {
        public IThermalService getThermalService() {
            return IThermalService.Stub.asInterface(
                    ServiceManager.getService(Context.THERMAL_SERVICE));
        }
    }
}
+64 −10
Original line number Diff line number Diff line
@@ -346,6 +346,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
    private boolean mAppliedTemporaryBrightness;
    private boolean mAppliedTemporaryAutoBrightnessAdjustment;
    private boolean mAppliedBrightnessBoost;
    private boolean mAppliedThrottling;

    // Reason for which the brightness was last changed. See {@link BrightnessReason} for more
    // information.
@@ -379,6 +380,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call

    private final HighBrightnessModeController mHbmController;

    private final BrightnessThrottler mBrightnessThrottler;

    private final BrightnessSetting mBrightnessSetting;

    private final Runnable mOnBrightnessChangeRunnable;
@@ -538,6 +541,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call

        mHbmController = createHbmControllerLocked();

        mBrightnessThrottler = createBrightnessThrottlerLocked();

        // Seed the cached brightness
        saveBrightnessInfo(getScreenBrightnessSetting());

@@ -827,6 +832,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
        reloadReduceBrightColours();
        mHbmController.resetHbmData(info.width, info.height, token, info.uniqueId,
                mDisplayDeviceConfig.getHighBrightnessModeData());
        mBrightnessThrottler.resetThrottlingData(
                mDisplayDeviceConfig.getBrightnessThrottlingData());
    }

    private void sendUpdatePowerState() {
@@ -1039,6 +1046,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
    private void cleanupHandlerThreadAfterStop() {
        setProximitySensorEnabled(false);
        mHbmController.stop();
        mBrightnessThrottler.stop();
        mHandler.removeCallbacksAndMessages(null);
        if (mUnfinishedBusiness) {
            mCallbacks.releaseSuspendBlocker();
@@ -1336,14 +1344,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
            mBrightnessReasonTemp.setReason(BrightnessReason.REASON_MANUAL);
        }

        // The current brightness to use has been calculated at this point (minus the adjustments
        // like low-power and dim), and HbmController should be notified so that it can accurately
        // calculate HDR or HBM levels. We specifically do it here instead of having HbmController
        // listen to the brightness setting because certain brightness sources (just as an app
        // override) are not saved to the setting, but should be reflected in HBM
        // calculations.
        mHbmController.onBrightnessChanged(brightnessState);

        if (updateScreenBrightnessSetting) {
            // Tell the rest of the system about the new brightness in case we had to change it
            // for things like auto-brightness or high-brightness-mode. Note that we do this
@@ -1390,6 +1390,28 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
            mAppliedLowPower = false;
        }

        // Apply brightness throttling after applying all other transforms
        final float unthrottledBrightnessState = brightnessState;
        if (mBrightnessThrottler.isThrottled()) {
            brightnessState = Math.min(brightnessState, mBrightnessThrottler.getBrightnessCap());
            mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_THROTTLED);
            if (!mAppliedThrottling) {
                slowChange = false;
            }
            mAppliedThrottling = true;
        } else if (mAppliedThrottling) {
            slowChange = false;
            mAppliedThrottling = false;
        }

        // The current brightness to use has been calculated at this point, and HbmController should
        // be notified so that it can accurately calculate HDR or HBM levels. We specifically do it
        // here instead of having HbmController listen to the brightness setting because certain
        // brightness sources (such as an app override) are not saved to the setting, but should be
        // reflected in HBM calculations.
        mHbmController.onBrightnessChanged(brightnessState, unthrottledBrightnessState,
                mBrightnessThrottler.getBrightnessMaxReason());

        // Animate the screen brightness when the screen is on or dozing.
        // Skip the animation when the screen is off or suspended or transition to/from VR.
        boolean brightnessAdjusted = false;
@@ -1441,6 +1463,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
            // use instead. We still preserve the calculated brightness for Standard Dynamic Range
            // (SDR) layers, but the main brightness value will be the one for HDR.
            float sdrAnimateValue = animateValue;
            // TODO(b/216365040): The decision to prevent HBM for HDR in low power mode should be
            // done in HighBrightnessModeController.
            if (mHbmController.getHighBrightnessMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR
                    && ((mBrightnessReason.modifier & BrightnessReason.MODIFIER_DIMMED) == 0
                    || (mBrightnessReason.modifier & BrightnessReason.MODIFIER_LOW_POWER) == 0)) {
@@ -1618,7 +1642,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
                    mCachedBrightnessInfo.brightnessMin.value,
                    mCachedBrightnessInfo.brightnessMax.value,
                    mCachedBrightnessInfo.hbmMode.value,
                    mCachedBrightnessInfo.hbmTransitionPoint.value);
                    mCachedBrightnessInfo.hbmTransitionPoint.value,
                    mCachedBrightnessInfo.brightnessMaxReason.value);
        }
    }

@@ -1648,6 +1673,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
            changed |=
                mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.hbmTransitionPoint,
                        mHbmController.getTransitionPoint());
            changed |=
                mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.brightnessMaxReason,
                        mBrightnessThrottler.getBrightnessMaxReason());

            return changed;
        }
@@ -1679,6 +1707,18 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
                }, mContext);
    }

    private BrightnessThrottler createBrightnessThrottlerLocked() {
        final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
        final DisplayDeviceConfig ddConfig = device.getDisplayDeviceConfig();
        final DisplayDeviceConfig.BrightnessThrottlingData data =
                ddConfig != null ? ddConfig.getBrightnessThrottlingData() : null;
        return new BrightnessThrottler(mHandler, data,
                () -> {
                    sendUpdatePowerStateLocked();
                    postBrightnessChangeRunnable();
                });
    }

    private void blockScreenOn() {
        if (mPendingScreenOnUnblocker == null) {
            Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, SCREEN_ON_BLOCKED_TRACE_NAME, 0);
@@ -2346,6 +2386,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
            pw.println("  mCachedBrightnessInfo.hbmMode=" + mCachedBrightnessInfo.hbmMode.value);
            pw.println("  mCachedBrightnessInfo.hbmTransitionPoint=" +
                    mCachedBrightnessInfo.hbmTransitionPoint.value);
            pw.println("  mCachedBrightnessInfo.brightnessMaxReason =" +
                    mCachedBrightnessInfo.brightnessMaxReason .value);
        }
        pw.println("  mDisplayBlanksAfterDozeConfig=" + mDisplayBlanksAfterDozeConfig);
        pw.println("  mBrightnessBucketsInDozeConfig=" + mBrightnessBucketsInDozeConfig);
@@ -2384,6 +2426,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
        pw.println("  mAppliedAutoBrightness=" + mAppliedAutoBrightness);
        pw.println("  mAppliedDimming=" + mAppliedDimming);
        pw.println("  mAppliedLowPower=" + mAppliedLowPower);
        pw.println("  mAppliedThrottling=" + mAppliedThrottling);
        pw.println("  mAppliedScreenBrightnessOverride=" + mAppliedScreenBrightnessOverride);
        pw.println("  mAppliedTemporaryBrightness=" + mAppliedTemporaryBrightness);
        pw.println("  mDozing=" + mDozing);
@@ -2422,6 +2465,10 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
            mHbmController.dump(pw);
        }

        if (mBrightnessThrottler != null) {
            mBrightnessThrottler.dump(pw);
        }

        pw.println();
        if (mDisplayWhiteBalanceController != null) {
            mDisplayWhiteBalanceController.dump(pw);
@@ -2702,7 +2749,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
        static final int MODIFIER_DIMMED = 0x1;
        static final int MODIFIER_LOW_POWER = 0x2;
        static final int MODIFIER_HDR = 0x4;
        static final int MODIFIER_MASK = MODIFIER_DIMMED | MODIFIER_LOW_POWER | MODIFIER_HDR;
        static final int MODIFIER_THROTTLED = 0x8;
        static final int MODIFIER_MASK = MODIFIER_DIMMED | MODIFIER_LOW_POWER | MODIFIER_HDR
            | MODIFIER_THROTTLED;

        // ADJUSTMENT_*
        // These things can happen at any point, even if the main brightness reason doesn't
@@ -2777,6 +2826,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
            if ((modifier & MODIFIER_HDR) != 0) {
                sb.append(" hdr");
            }
            if ((modifier & MODIFIER_THROTTLED) != 0) {
                sb.append(" throttled");
            }
            int strlen = sb.length();
            if (sb.charAt(strlen - 1) == '[') {
                sb.setLength(strlen - 2);
@@ -2813,6 +2865,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
        public MutableInt hbmMode = new MutableInt(BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF);
        public MutableFloat hbmTransitionPoint =
            new MutableFloat(HighBrightnessModeController.HBM_TRANSITION_POINT_INVALID);
        public MutableInt brightnessMaxReason =
            new MutableInt(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE);

        public boolean checkAndSetFloat(MutableFloat mf, float f) {
            if (mf.value != f) {
+34 −7

File changed.

Preview size limit exceeded, changes collapsed.

+215 −12

File changed.

Preview size limit exceeded, changes collapsed.

Loading