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

Commit 8f1a5c14 authored by Philip Junker's avatar Philip Junker Committed by Santos Cordon
Browse files

Add low-power/thermal checks to highBrightnessMode

Adds an observer to HighBrightnessModeController to monitor the system's
thermal status and ensure HBM is off if the status is above the new
limit defined in the display-device-config file.

Also fixes a bug where AutomaticBrightnessController does not
automatically recalculate a value when HBM mode changes and the ambient
lux does not.

Bug: 179019497
Test: atest HighBrightnessModeControllerTest
Change-Id: I38a805671cee0f63e858b7ec0b6a45ca43df1737
parent 3f32db86
Loading
Loading
Loading
Loading
+12 −0
Original line number Diff line number Diff line
@@ -68,6 +68,7 @@ class AutomaticBrightnessController {
    private static final int MSG_INVALIDATE_SHORT_TERM_MODEL = 3;
    private static final int MSG_UPDATE_FOREGROUND_APP = 4;
    private static final int MSG_UPDATE_FOREGROUND_APP_SYNC = 5;
    private static final int MSG_RUN_UPDATE = 6;

    // Length of the ambient light horizon used to calculate the long term estimate of ambient
    // light.
@@ -360,6 +361,13 @@ class AutomaticBrightnessController {
        return mBrightnessMapper.getDefaultConfig();
    }

    /**
     * Force recalculate of the state of automatic brightness.
     */
    public void update() {
        mHandler.sendEmptyMessage(MSG_RUN_UPDATE);
    }

    private boolean setDisplayPolicy(int policy) {
        if (mDisplayPolicy == policy) {
            return false;
@@ -910,6 +918,10 @@ class AutomaticBrightnessController {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_RUN_UPDATE:
                    updateAutoBrightness(true /*sendUpdate*/, false /*isManuallySet*/);
                    break;

                case MSG_UPDATE_AMBIENT_LUX:
                    updateAmbientLux();
                    break;
+44 −3
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@ import com.android.server.display.config.NitsMap;
import com.android.server.display.config.Point;
import com.android.server.display.config.RefreshRateRange;
import com.android.server.display.config.SensorDetails;
import com.android.server.display.config.ThermalStatus;
import com.android.server.display.config.XmlParser;

import org.xmlpull.v1.XmlPullParserException;
@@ -657,6 +658,8 @@ public class DisplayDeviceConfig {
            mHbmData.timeWindowMillis = hbmTiming.getTimeWindowSecs_all().longValue() * 1000;
            mHbmData.timeMaxMillis = hbmTiming.getTimeMaxSecs_all().longValue() * 1000;
            mHbmData.timeMinMillis = hbmTiming.getTimeMinSecs_all().longValue() * 1000;
            mHbmData.thermalStatusLimit = convertThermalStatus(hbm.getThermalStatusLimit_all());
            mHbmData.allowInLowPowerMode = hbm.getAllowInLowPowerMode_all();
            final RefreshRateRange rr = hbm.getRefreshRate_all();
            if (rr != null) {
                final float min = rr.getMinimum().floatValue();
@@ -743,6 +746,31 @@ public class DisplayDeviceConfig {
        }
    }

    private @PowerManager.ThermalStatus int convertThermalStatus(ThermalStatus value) {
        if (value == null) {
            return PowerManager.THERMAL_STATUS_NONE;
        }
        switch (value) {
            case none:
                return PowerManager.THERMAL_STATUS_NONE;
            case light:
                return PowerManager.THERMAL_STATUS_LIGHT;
            case moderate:
                return PowerManager.THERMAL_STATUS_MODERATE;
            case severe:
                return PowerManager.THERMAL_STATUS_SEVERE;
            case critical:
                return PowerManager.THERMAL_STATUS_CRITICAL;
            case emergency:
                return PowerManager.THERMAL_STATUS_EMERGENCY;
            case shutdown:
                return PowerManager.THERMAL_STATUS_SHUTDOWN;
            default:
                Slog.wtf(TAG, "Unexpected Thermal Status: " + value);
                return PowerManager.THERMAL_STATUS_NONE;
        }
    }

    static class SensorData {
        public String type;
        public String name;
@@ -781,6 +809,12 @@ public class DisplayDeviceConfig {
        /** Brightness level at which we transition from normal to high-brightness. */
        public float transitionPoint;

        /** Enable HBM only if the thermal status is not higher than this. */
        public @PowerManager.ThermalStatus int thermalStatusLimit;

        /** Whether HBM is allowed when {@code Settings.Global.LOW_POWER_MODE} is active. */
        public boolean allowInLowPowerMode;

        /** Time window for HBM. */
        public long timeWindowMillis;

@@ -792,13 +826,16 @@ public class DisplayDeviceConfig {

        HighBrightnessModeData() {}

        HighBrightnessModeData(float minimumLux, float transitionPoint,
                long timeWindowMillis, long timeMaxMillis, long timeMinMillis) {
        HighBrightnessModeData(float minimumLux, float transitionPoint, long timeWindowMillis,
                long timeMaxMillis, long timeMinMillis,
                @PowerManager.ThermalStatus int thermalStatusLimit, boolean allowInLowPowerMode) {
            this.minimumLux = minimumLux;
            this.transitionPoint = transitionPoint;
            this.timeWindowMillis = timeWindowMillis;
            this.timeMaxMillis = timeMaxMillis;
            this.timeMinMillis = timeMinMillis;
            this.thermalStatusLimit = thermalStatusLimit;
            this.allowInLowPowerMode = allowInLowPowerMode;
        }

        /**
@@ -807,10 +844,12 @@ public class DisplayDeviceConfig {
         */
        public void copyTo(@NonNull HighBrightnessModeData other) {
            other.minimumLux = minimumLux;
            other.transitionPoint = transitionPoint;
            other.timeWindowMillis = timeWindowMillis;
            other.timeMaxMillis = timeMaxMillis;
            other.timeMinMillis = timeMinMillis;
            other.transitionPoint = transitionPoint;
            other.thermalStatusLimit = thermalStatusLimit;
            other.allowInLowPowerMode = allowInLowPowerMode;
        }

        @Override
@@ -821,6 +860,8 @@ public class DisplayDeviceConfig {
                    + ", timeWindow: " + timeWindowMillis + "ms"
                    + ", timeMax: " + timeMaxMillis + "ms"
                    + ", timeMin: " + timeMinMillis + "ms"
                    + ", thermalStatusLimit: " + thermalStatusLimit
                    + ", allowInLowPowerMode: " + allowInLowPowerMode
                    + "} ";
        }
    }
+3 −1
Original line number Diff line number Diff line
@@ -1518,7 +1518,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
                () -> {
                    sendUpdatePowerStateLocked();
                    mHandler.post(mOnBrightnessChangeRunnable);
                });
                    // TODO(b/192258832): Switch the HBMChangeCallback to a listener pattern.
                    mAutomaticBrightnessController.update();
                }, mContext);
    }

    private void blockScreenOn() {
+193 −10
Original line number Diff line number Diff line
@@ -16,11 +16,21 @@

package com.android.server.display;

import android.content.Context;
import android.database.ContentObserver;
import android.hardware.display.BrightnessInfo;
import android.net.Uri;
import android.os.Handler;
import android.os.IBinder;
import android.os.IThermalEventListener;
import android.os.IThermalService;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.Temperature;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Slog;
import android.util.TimeUtils;
import android.view.SurfaceControlHdrLayerInfoListener;
@@ -52,6 +62,10 @@ class HighBrightnessModeController {
    private final Runnable mHbmChangeCallback;
    private final Runnable mRecalcRunnable;
    private final Clock mClock;
    private final SkinThermalStatusObserver mSkinThermalStatusObserver;
    private final Context mContext;
    private final SettingsObserver mSettingsObserver;
    private final Injector mInjector;

    private SurfaceControlHdrLayerInfoListener mHdrListener;
    private HighBrightnessModeData mHbmData;
@@ -63,6 +77,8 @@ class HighBrightnessModeController {
    private float mAutoBrightness;
    private int mHbmMode = BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
    private boolean mIsHdrLayerPresent = false;
    private boolean mIsThermalStatusWithinLimit = true;
    private boolean mIsBlockedByLowPowerMode = false;

    /**
     * If HBM is currently running, this is the start time for the current HBM session.
@@ -72,29 +88,33 @@ class HighBrightnessModeController {
    /**
     * List of previous HBM-events ordered from most recent to least recent.
     * Meant to store only the events that fall into the most recent
     * {@link mHbmData.timeWindowSecs}.
     * {@link mHbmData.timeWindowMillis}.
     */
    private LinkedList<HbmEvent> mEvents = new LinkedList<>();

    HighBrightnessModeController(Handler handler, IBinder displayToken, float brightnessMin,
            float brightnessMax, HighBrightnessModeData hbmData, Runnable hbmChangeCallback) {
        this(SystemClock::uptimeMillis, handler, displayToken, brightnessMin, brightnessMax,
                hbmData, hbmChangeCallback);
            float brightnessMax, HighBrightnessModeData hbmData, Runnable hbmChangeCallback,
            Context context) {
        this(new Injector(), handler, displayToken, brightnessMin, brightnessMax,
                hbmData, hbmChangeCallback, context);
    }

    @VisibleForTesting
    HighBrightnessModeController(Clock clock, Handler handler, IBinder displayToken,
    HighBrightnessModeController(Injector injector, Handler handler, IBinder displayToken,
            float brightnessMin, float brightnessMax, HighBrightnessModeData hbmData,
            Runnable hbmChangeCallback) {
        mClock = clock;
            Runnable hbmChangeCallback, Context context) {
        mInjector = injector;
        mClock = injector.getClock();
        mHandler = handler;
        mBrightnessMin = brightnessMin;
        mBrightnessMax = brightnessMax;
        mHbmChangeCallback = hbmChangeCallback;
        mContext = context;
        mAutoBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
        mRecalcRunnable = this::recalculateTimeAllowance;
        mHdrListener = new HdrListener();

        mSkinThermalStatusObserver = new SkinThermalStatusObserver(mInjector, mHandler);
        mSettingsObserver = new SettingsObserver(mHandler);
        resetHbmData(displayToken, hbmData);
    }

@@ -178,14 +198,26 @@ class HighBrightnessModeController {

    void stop() {
        registerHdrListener(null /*displayToken*/);
        mSkinThermalStatusObserver.stopObserving();
        mSettingsObserver.stopObserving();
    }

    void resetHbmData(IBinder displayToken, HighBrightnessModeData hbmData) {
        mHbmData = hbmData;
        unregisterHdrListener();
        mSkinThermalStatusObserver.stopObserving();
        mSettingsObserver.stopObserving();
        if (deviceSupportsHbm()) {
            registerHdrListener(displayToken);
            recalculateTimeAllowance();
            if (mHbmData.thermalStatusLimit > PowerManager.THERMAL_STATUS_NONE) {
                mIsThermalStatusWithinLimit = true;
                mSkinThermalStatusObserver.startObserving();
            }
            if (!mHbmData.allowInLowPowerMode) {
                mIsBlockedByLowPowerMode = false;
                mSettingsObserver.startObserving();
            }
        }
    }

@@ -208,6 +240,8 @@ class HighBrightnessModeController {
        pw.println("  mBrightnessMin=" + mBrightnessMin);
        pw.println("  mBrightnessMax=" + mBrightnessMax);
        pw.println("  mRunningStartTimeMillis=" + TimeUtils.formatUptime(mRunningStartTimeMillis));
        pw.println("  mIsThermalStatusWithinLimit=" + mIsThermalStatusWithinLimit);
        pw.println("  mIsBlockedByLowPowerMode=" + mIsBlockedByLowPowerMode);
        pw.println("  mEvents=");
        final long currentTime = mClock.uptimeMillis();
        long lastStartTime = currentTime;
@@ -221,6 +255,8 @@ class HighBrightnessModeController {
            }
            lastStartTime = dumpHbmEvent(pw, event);
        }

        mSkinThermalStatusObserver.dump(pw);
    }

    private long dumpHbmEvent(PrintWriter pw, HbmEvent event) {
@@ -234,7 +270,8 @@ class HighBrightnessModeController {

    private boolean isCurrentlyAllowed() {
        return mIsHdrLayerPresent
                || (mIsAutoBrightnessEnabled && mIsTimeAvailable && mIsInAllowedAmbientRange);
                || (mIsAutoBrightnessEnabled && mIsTimeAvailable && mIsInAllowedAmbientRange
                && mIsThermalStatusWithinLimit && !mIsBlockedByLowPowerMode);
    }

    private boolean deviceSupportsHbm() {
@@ -327,6 +364,12 @@ class HighBrightnessModeController {
                    + ", remainingAllowedTime: " + remainingTime
                    + ", isLuxHigh: " + mIsInAllowedAmbientRange
                    + ", isHBMCurrentlyAllowed: " + isCurrentlyAllowed()
                    + ", isHdrLayerPresent: " + mIsHdrLayerPresent
                    + ", isAutoBrightnessEnabled: " +  mIsAutoBrightnessEnabled
                    + ", mIsTimeAvailable: " + mIsTimeAvailable
                    + ", mIsInAllowedAmbientRange: " + mIsInAllowedAmbientRange
                    + ", mIsThermalStatusWithinLimit: " + mIsThermalStatusWithinLimit
                    + ", mIsBlockedByLowPowerMode: " + mIsBlockedByLowPowerMode
                    + ", brightness: " + mAutoBrightness
                    + ", RunningStartTimeMillis: " + mRunningStartTimeMillis
                    + ", nextTimeout: " + (nextTimeout != -1 ? (nextTimeout - currentTime) : -1)
@@ -337,8 +380,11 @@ class HighBrightnessModeController {
            mHandler.removeCallbacks(mRecalcRunnable);
            mHandler.postAtTime(mRecalcRunnable, nextTimeout + 1);
        }

        // Update the state of the world
        updateHbmMode();
    }

    private void updateHbmMode() {
        int newHbmMode = calculateHighBrightnessMode();
        if (mHbmMode != newHbmMode) {
            mHbmMode = newHbmMode;
@@ -409,4 +455,141 @@ class HighBrightnessModeController {
            });
        }
    }

    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 "
                        + ", current thermal status = " + temp.getStatus()
                        + ", threshold = " + mHbmData.thermalStatusLimit);
            }
            mHandler.post(() -> {
                mIsThermalStatusWithinLimit = temp.getStatus() <= mHbmData.thermalStatusLimit;
                // This recalculates HbmMode and runs mHbmChangeCallback if the mode has changed
                updateHbmMode();
            });
        }

        void startObserving() {
            if (mStarted) {
                if (DEBUG) {
                    Slog.d(TAG, "Thermal status observer already started");
                }
                return;
            }
            mThermalService = mInjector.getThermalService();
            if (mThermalService == null) {
                Slog.w(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() {
            mIsThermalStatusWithinLimit = true;
            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");
            }
        }
    }

    private final class SettingsObserver extends ContentObserver {
        private final Uri mLowPowerModeSetting = Settings.Global.getUriFor(
                Settings.Global.LOW_POWER_MODE);
        private boolean mStarted;

        SettingsObserver(Handler handler) {
            super(handler);
        }

        @Override
        public void onChange(boolean selfChange, Uri uri) {
            updateLowPower();
        }

        void startObserving() {
            if (!mStarted) {
                mContext.getContentResolver().registerContentObserver(mLowPowerModeSetting,
                        false /*notifyForDescendants*/, this, UserHandle.USER_ALL);
                mStarted = true;
                updateLowPower();
            }
        }

        void stopObserving() {
            mIsBlockedByLowPowerMode = false;
            if (mStarted) {
                mContext.getContentResolver().unregisterContentObserver(this);
                mStarted = false;
            }
        }

        private void updateLowPower() {
            final boolean isLowPowerMode = isLowPowerMode();
            if (isLowPowerMode == mIsBlockedByLowPowerMode) {
                return;
            }
            if (DEBUG) {
                Slog.d(TAG, "Settings.Global.LOW_POWER_MODE enabled: " + isLowPowerMode);
            }
            mIsBlockedByLowPowerMode = isLowPowerMode;
            // this recalculates HbmMode and runs mHbmChangeCallback if the mode has changed
            updateHbmMode();
        }

        private boolean isLowPowerMode() {
            return Settings.Global.getInt(
                    mContext.getContentResolver(), Settings.Global.LOW_POWER_MODE, 0) != 0;
        }
    }

    public static class Injector {
        public Clock getClock() {
            return SystemClock::uptimeMillis;
        }

        public IThermalService getThermalService() {
            return IThermalService.Stub.asInterface(
                    ServiceManager.getService(Context.THERMAL_SERVICE));
        }
    }
}
+23 −0
Original line number Diff line number Diff line
@@ -81,6 +81,16 @@
                <xs:annotation name="nullable"/>
                <xs:annotation name="final"/>
            </xs:element>
            <!-- The highest (most severe) thermal status at which high-brightness-mode is allowed
                 to operate. -->
            <xs:element name="thermalStatusLimit" type="thermalStatus" minOccurs="0" maxOccurs="1">
                <xs:annotation name="nonnull"/>
                <xs:annotation name="final"/>
            </xs:element>
            <xs:element name="allowInLowPowerMode" type="xs:boolean" minOccurs="0" maxOccurs="1">
                <xs:annotation name="nonnull"/>
                <xs:annotation name="final"/>
            </xs:element>
        </xs:all>
        <xs:attribute name="enabled" type="xs:boolean" use="optional"/>
    </xs:complexType>
@@ -102,6 +112,19 @@
        </xs:all>
    </xs:complexType>

    <!-- Maps to PowerManager.THERMAL_STATUS_* values. -->
    <xs:simpleType name="thermalStatus">
        <xs:restriction base="xs:string">
            <xs:enumeration value="none"/>
            <xs:enumeration value="light"/>
            <xs:enumeration value="moderate"/>
            <xs:enumeration value="severe"/>
            <xs:enumeration value="critical"/>
            <xs:enumeration value="emergency"/>
            <xs:enumeration value="shutdown"/>
        </xs:restriction>
    </xs:simpleType>

    <xs:complexType name="nitsMap">
        <xs:sequence>
            <xs:element name="point" type="point" maxOccurs="unbounded" minOccurs="2">
Loading