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

Commit f654a3ce authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge changes from topic "brightnessthrottler"

* changes:
  Add display brightness throttler
  Add BrightnessThrottler data to display device config
  Report HBM_ON_SUNLIGHT when display is in hbm mode
parents b9314e16 87546f50
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));
        }
    }
}
+155 −0
Original line number Diff line number Diff line
@@ -33,6 +33,8 @@ import android.view.DisplayAddress;
import com.android.internal.R;
import com.android.internal.display.BrightnessSynchronizer;
import com.android.server.display.config.BrightnessThresholds;
import com.android.server.display.config.BrightnessThrottlingMap;
import com.android.server.display.config.BrightnessThrottlingPoint;
import com.android.server.display.config.Density;
import com.android.server.display.config.DisplayConfiguration;
import com.android.server.display.config.DisplayQuirks;
@@ -43,6 +45,7 @@ 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.ThermalThrottling;
import com.android.server.display.config.Thresholds;
import com.android.server.display.config.XmlParser;

@@ -145,6 +148,8 @@ public class DisplayDeviceConfig {
    private DensityMap mDensityMap;
    private String mLoadedFrom = null;

    private BrightnessThrottlingData mBrightnessThrottlingData;

    private DisplayDeviceConfig(Context context) {
        mContext = context;
    }
@@ -424,6 +429,13 @@ public class DisplayDeviceConfig {
        return mDensityMap;
    }

    /**
     * @return brightness throttling data configuration data for the display.
     */
    public BrightnessThrottlingData getBrightnessThrottlingData() {
        return BrightnessThrottlingData.create(mBrightnessThrottlingData);
    }

    @Override
    public String toString() {
        return "DisplayDeviceConfig{"
@@ -441,6 +453,7 @@ public class DisplayDeviceConfig {
                + ", mQuirks=" + mQuirks
                + ", isHbmEnabled=" + mIsHighBrightnessModeEnabled
                + ", mHbmData=" + mHbmData
                + ", mBrightnessThrottlingData=" + mBrightnessThrottlingData
                + ", mBrightnessRampFastDecrease=" + mBrightnessRampFastDecrease
                + ", mBrightnessRampFastIncrease=" + mBrightnessRampFastIncrease
                + ", mBrightnessRampSlowDecrease=" + mBrightnessRampSlowDecrease
@@ -502,6 +515,7 @@ public class DisplayDeviceConfig {
                loadBrightnessDefaultFromDdcXml(config);
                loadBrightnessConstraintsFromConfigXml();
                loadBrightnessMap(config);
                loadBrightnessThrottlingMap(config);
                loadHighBrightnessModeData(config);
                loadQuirks(config);
                loadBrightnessRamps(config);
@@ -664,6 +678,41 @@ public class DisplayDeviceConfig {
        constrainNitsAndBacklightArrays();
    }

    private void loadBrightnessThrottlingMap(DisplayConfiguration config) {
        final ThermalThrottling throttlingConfig = config.getThermalThrottling();
        if (throttlingConfig == null) {
            Slog.i(TAG, "no thermal throttling config found");
            return;
        }

        final BrightnessThrottlingMap map = throttlingConfig.getBrightnessThrottlingMap();
        if (map == null) {
            Slog.i(TAG, "no brightness throttling map found");
            return;
        }

        final List<BrightnessThrottlingPoint> points = map.getBrightnessThrottlingPoint();
        // At least 1 point is guaranteed by the display device config schema
        List<BrightnessThrottlingData.ThrottlingLevel> throttlingLevels =
            new ArrayList<>(points.size());

        boolean badConfig = false;
        for (BrightnessThrottlingPoint point : points) {
            ThermalStatus status = point.getThermalStatus();
            if (!thermalStatusIsValid(status)) {
                badConfig = true;
                break;
            }

            throttlingLevels.add(new BrightnessThrottlingData.ThrottlingLevel(
                convertThermalStatus(status), point.getBrightness().floatValue()));
        }

        if (!badConfig) {
            mBrightnessThrottlingData = BrightnessThrottlingData.create(throttlingLevels);
        }
    }

    private void loadBrightnessMapFromConfigXml() {
        // Use the config.xml mapping
        final Resources res = mContext.getResources();
@@ -931,6 +980,25 @@ public class DisplayDeviceConfig {
        }
    }

    private boolean thermalStatusIsValid(ThermalStatus value) {
        if (value == null) {
            return false;
        }

        switch (value) {
            case none:
            case light:
            case moderate:
            case severe:
            case critical:
            case emergency:
            case shutdown:
                return true;
            default:
                return false;
        }
    }

    private @PowerManager.ThermalStatus int convertThermalStatus(ThermalStatus value) {
        if (value == null) {
            return PowerManager.THERMAL_STATUS_NONE;
@@ -1061,4 +1129,91 @@ public class DisplayDeviceConfig {
                    + "} ";
        }
    }

    /**
     * Container for brightness throttling data.
     */
    static class BrightnessThrottlingData {
        static class ThrottlingLevel {
            public @PowerManager.ThermalStatus int thermalStatus;
            public float brightness;

            ThrottlingLevel(@PowerManager.ThermalStatus int thermalStatus, float brightness) {
                this.thermalStatus = thermalStatus;
                this.brightness = brightness;
            }

            @Override
            public String toString() {
                return "[" + thermalStatus + "," + brightness + "]";
            }
        }

        public List<ThrottlingLevel> throttlingLevels;

        static public BrightnessThrottlingData create(List<ThrottlingLevel> throttlingLevels)
        {
            if (throttlingLevels == null || throttlingLevels.size() == 0) {
                Slog.e(TAG, "BrightnessThrottlingData received null or empty throttling levels");
                return null;
            }

            ThrottlingLevel prevLevel = throttlingLevels.get(0);
            final int numLevels = throttlingLevels.size();
            for (int i = 1; i < numLevels; i++) {
                ThrottlingLevel thisLevel = throttlingLevels.get(i);

                if (thisLevel.thermalStatus <= prevLevel.thermalStatus) {
                    Slog.e(TAG, "brightnessThrottlingMap must be strictly increasing, ignoring "
                            + "configuration. ThermalStatus " + thisLevel.thermalStatus + " <= "
                            + prevLevel.thermalStatus);
                    return null;
                }

                if (thisLevel.brightness >= prevLevel.brightness) {
                    Slog.e(TAG, "brightnessThrottlingMap must be strictly decreasing, ignoring "
                            + "configuration. Brightness " + thisLevel.brightness + " >= "
                            + thisLevel.brightness);
                    return null;
                }

                prevLevel = thisLevel;
            }

            for (ThrottlingLevel level : throttlingLevels) {
                // Non-negative brightness values are enforced by device config schema
                if (level.brightness > PowerManager.BRIGHTNESS_MAX) {
                    Slog.e(TAG, "brightnessThrottlingMap contains a brightness value exceeding "
                            + "system max. Brightness " + level.brightness + " > "
                            + PowerManager.BRIGHTNESS_MAX);
                    return null;
                }
            }

            return new BrightnessThrottlingData(throttlingLevels);
        }

        static public BrightnessThrottlingData create(BrightnessThrottlingData other) {
            if (other == null)
                return null;

            return BrightnessThrottlingData.create(other.throttlingLevels);
        }


        @Override
        public String toString() {
            return "BrightnessThrottlingData{"
                + "throttlingLevels:" + throttlingLevels
                + "} ";
        }

        private BrightnessThrottlingData(List<ThrottlingLevel> inLevels) {
            throttlingLevels = new ArrayList<>(inLevels.size());
            for (ThrottlingLevel level : inLevels) {
                throttlingLevels.add(new ThrottlingLevel(level.thermalStatus, level.brightness));
            }
        }

    }
}
+64 −10

File changed.

Preview size limit exceeded, changes collapsed.

+40 −10

File changed.

Preview size limit exceeded, changes collapsed.

Loading