diff --git a/core/java/android/hardware/display/BrightnessInfo.java b/core/java/android/hardware/display/BrightnessInfo.java index 0dc8f92967fb9..99f3d156cfa95 100644 --- a/core/java/android/hardware/display/BrightnessInfo.java +++ b/core/java/android/hardware/display/BrightnessInfo.java @@ -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 CREATOR = @@ -145,6 +184,7 @@ public final class BrightnessInfo implements Parcelable { brightnessMaximum = source.readFloat(); highBrightnessMode = source.readInt(); highBrightnessTransitionPoint = source.readFloat(); + brightnessMaxReason = source.readInt(); } } diff --git a/services/core/java/com/android/server/display/BrightnessThrottler.java b/services/core/java/com/android/server/display/BrightnessThrottler.java new file mode 100644 index 0000000000000..767b2d18a69a0 --- /dev/null +++ b/services/core/java/com/android/server/display/BrightnessThrottler.java @@ -0,0 +1,262 @@ +/* + * 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)); + } + } +} diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index ec4b91a79ae62..1f448549c59c7 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -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) { diff --git a/services/core/java/com/android/server/display/HighBrightnessModeController.java b/services/core/java/com/android/server/display/HighBrightnessModeController.java index 5f8a4ab71e88e..534ed5d8491a9 100644 --- a/services/core/java/com/android/server/display/HighBrightnessModeController.java +++ b/services/core/java/com/android/server/display/HighBrightnessModeController.java @@ -82,7 +82,15 @@ class HighBrightnessModeController { private boolean mIsTimeAvailable = false; private boolean mIsAutoBrightnessEnabled = false; private boolean mIsAutoBrightnessOffByState = false; + + // The following values are typically reported by DisplayPowerController. + // This value includes brightness throttling effects. private float mBrightness; + // This value excludes brightness throttling effects. + private float mUnthrottledBrightness; + private @BrightnessInfo.BrightnessMaxReason int mThrottlingReason = + BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE; + private int mHbmMode = BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF; private boolean mIsHdrLayerPresent = false; private boolean mIsThermalStatusWithinLimit = true; @@ -192,11 +200,14 @@ class HighBrightnessModeController { } } - void onBrightnessChanged(float brightness) { + void onBrightnessChanged(float brightness, float unthrottledBrightness, + @BrightnessInfo.BrightnessMaxReason int throttlingReason) { if (!deviceSupportsHbm()) { return; } mBrightness = brightness; + mUnthrottledBrightness = unthrottledBrightness; + mThrottlingReason = throttlingReason; // If we are starting or ending a high brightness mode session, store the current // session in mRunningStartTimeMillis, or the old one in mEvents. @@ -274,6 +285,8 @@ class HighBrightnessModeController { private void dumpLocal(PrintWriter pw) { pw.println("HighBrightnessModeController:"); pw.println(" mBrightness=" + mBrightness); + pw.println(" mUnthrottledBrightness=" + mUnthrottledBrightness); + pw.println(" mThrottlingReason=" + BrightnessInfo.briMaxReasonToString(mThrottlingReason)); pw.println(" mCurrentMin=" + getCurrentBrightnessMin()); pw.println(" mCurrentMax=" + getCurrentBrightnessMax()); pw.println(" mHbmMode=" + BrightnessInfo.hbmToString(mHbmMode) @@ -431,6 +444,9 @@ class HighBrightnessModeController { + ", mIsThermalStatusWithinLimit: " + mIsThermalStatusWithinLimit + ", mIsBlockedByLowPowerMode: " + mIsBlockedByLowPowerMode + ", mBrightness: " + mBrightness + + ", mUnthrottledBrightness: " + mUnthrottledBrightness + + ", mThrottlingReason: " + + BrightnessInfo.briMaxReasonToString(mThrottlingReason) + ", RunningStartTimeMillis: " + mRunningStartTimeMillis + ", nextTimeout: " + (nextTimeout != -1 ? (nextTimeout - currentTime) : -1) + ", events: " + mEvents); @@ -454,12 +470,13 @@ class HighBrightnessModeController { } private void updateHbmStats(int newMode) { + final float transitionPoint = mHbmData.transitionPoint; int state = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF; if (newMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR - && getHdrBrightnessValue() > mHbmData.transitionPoint) { + && getHdrBrightnessValue() > transitionPoint) { state = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR; } else if (newMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT - && mBrightness > mHbmData.transitionPoint) { + && mBrightness > transitionPoint) { state = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT; } if (state == mHbmStatsState) { @@ -468,11 +485,21 @@ class HighBrightnessModeController { int reason = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN; - boolean oldHbmSv = (mHbmStatsState + final boolean oldHbmSv = (mHbmStatsState == FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT); - boolean newHbmSv = + final boolean newHbmSv = (state == FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT); if (oldHbmSv && !newHbmSv) { + // HighBrightnessModeController (HBMC) currently supports throttling from two sources: + // 1. Internal, received from HBMC.SkinThermalStatusObserver.notifyThrottling() + // 2. External, received from HBMC.onBrightnessChanged() + // TODO(b/216373254): Deprecate internal throttling source + final boolean internalThermalThrottling = !mIsThermalStatusWithinLimit; + final boolean externalThermalThrottling = + mUnthrottledBrightness > transitionPoint && // We would've liked HBM brightness... + mBrightness <= transitionPoint && // ...but we got NBM, because of... + mThrottlingReason == BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL; // ...thermals. + // If more than one conditions are flipped and turn off HBM sunlight // visibility, only one condition will be reported to make it simple. if (!mIsAutoBrightnessEnabled && mIsAutoBrightnessOffByState) { @@ -485,7 +512,7 @@ class HighBrightnessModeController { reason = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_LUX_DROP; } else if (!mIsTimeAvailable) { reason = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_TIME_LIMIT; - } else if (!mIsThermalStatusWithinLimit) { + } else if (internalThermalThrottling || externalThermalThrottling) { reason = FrameworkStatsLog .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_THERMAL_LIMIT; } else if (mIsHdrLayerPresent) { @@ -575,7 +602,7 @@ class HighBrightnessModeController { >= ((float) (mWidth * mHeight) * HDR_PERCENT_OF_SCREEN_REQUIRED); // Calling the brightness update so that we can recalculate // brightness with HDR in mind. - onBrightnessChanged(mBrightness); + onBrightnessChanged(mBrightness, mUnthrottledBrightness, mThrottlingReason); }); } } diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java index a65b41d68d551..0ed90d27db996 100644 --- a/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java +++ b/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java @@ -18,21 +18,40 @@ package com.android.server.display; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import android.hardware.display.BrightnessInfo; +import android.os.Handler; +import android.os.IThermalEventListener; +import android.os.IThermalService; +import android.os.Message; import android.os.PowerManager; +import android.os.Temperature.ThrottlingStatus; +import android.os.Temperature; +import android.os.test.TestLooper; import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.server.display.BrightnessThrottler.Injector; import com.android.server.display.DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel; import com.android.server.display.DisplayDeviceConfig.BrightnessThrottlingData; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.ArrayList; @@ -43,10 +62,28 @@ import java.util.List; @Presubmit @RunWith(AndroidJUnit4.class) public class BrightnessThrottlerTest { + private static final float EPSILON = 0.000001f; + + private Handler mHandler; + private TestLooper mTestLooper; + + @Mock IThermalService mThermalServiceMock; + @Mock Injector mInjectorMock; + + @Captor ArgumentCaptor mThermalEventListenerCaptor; @Before public void setUp() { MockitoAnnotations.initMocks(this); + when(mInjectorMock.getThermalService()).thenReturn(mThermalServiceMock); + mTestLooper = new TestLooper(); + mHandler = new Handler(mTestLooper.getLooper(), new Handler.Callback() { + @Override + public boolean handleMessage(Message msg) { + return true; + } + }); + } ///////////////// @@ -62,19 +99,23 @@ public class BrightnessThrottlerTest { validLevels.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.62f)); validLevels.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.25f)); - List invalidThermalLevels = new ArrayList<>(); - invalidThermalLevels.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.62f)); - invalidThermalLevels.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.25f)); + List unsortedThermalLevels = new ArrayList<>(); + unsortedThermalLevels.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.62f)); + unsortedThermalLevels.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.25f)); - List invalidBrightnessLevels = new ArrayList<>(); - invalidBrightnessLevels.add( + List unsortedBrightnessLevels = new ArrayList<>(); + unsortedBrightnessLevels.add( new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.25f)); - invalidBrightnessLevels.add( + unsortedBrightnessLevels.add( new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.62f)); - List invalidLevels = new ArrayList<>(); - invalidLevels.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.25f)); - invalidLevels.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.62f)); + List unsortedLevels = new ArrayList<>(); + unsortedLevels.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.25f)); + unsortedLevels.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.62f)); + + List invalidLevel = new ArrayList<>(); + invalidLevel.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, + PowerManager.BRIGHTNESS_MAX + EPSILON)); // Test invalid data BrightnessThrottlingData data; @@ -84,11 +125,13 @@ public class BrightnessThrottlerTest { assertEquals(data, null); data = BrightnessThrottlingData.create(new ArrayList()); assertEquals(data, null); - data = BrightnessThrottlingData.create(invalidThermalLevels); + data = BrightnessThrottlingData.create(unsortedThermalLevels); assertEquals(data, null); - data = BrightnessThrottlingData.create(invalidBrightnessLevels); + data = BrightnessThrottlingData.create(unsortedBrightnessLevels); assertEquals(data, null); - data = BrightnessThrottlingData.create(invalidLevels); + data = BrightnessThrottlingData.create(unsortedLevels); + assertEquals(data, null); + data = BrightnessThrottlingData.create(invalidLevel); assertEquals(data, null); // Test valid data @@ -101,6 +144,154 @@ public class BrightnessThrottlerTest { assertThrottlingLevelsEquals(validLevels, data.throttlingLevels); } + @Test + public void testThrottlingUnsupported() throws Exception { + final BrightnessThrottler throttler = createThrottlerUnsupported(); + assertFalse(throttler.deviceSupportsThrottling()); + + // Thermal listener shouldn't be registered if throttling is unsupported + verify(mInjectorMock, never()).getThermalService(); + + // Ensure that brightness is uncapped when the device doesn't support throttling + assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f); + } + + @Test + public void testThrottlingSingleLevel() throws Exception { + final ThrottlingLevel level = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, + 0.25f); + + List levels = new ArrayList<>(); + levels.add(level); + final BrightnessThrottlingData data = BrightnessThrottlingData.create(levels); + final BrightnessThrottler throttler = createThrottlerSupported(data); + assertTrue(throttler.deviceSupportsThrottling()); + + verify(mThermalServiceMock).registerThermalEventListenerWithType( + mThermalEventListenerCaptor.capture(), eq(Temperature.TYPE_SKIN)); + final IThermalEventListener listener = mThermalEventListenerCaptor.getValue(); + + // Set status too low to trigger throttling + listener.notifyThrottling(getSkinTemp(level.thermalStatus - 1)); + mTestLooper.dispatchAll(); + assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f); + assertFalse(throttler.isThrottled()); + assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE, throttler.getBrightnessMaxReason()); + + // Set status just high enough to trigger throttling + listener.notifyThrottling(getSkinTemp(level.thermalStatus)); + mTestLooper.dispatchAll(); + assertEquals(level.brightness, throttler.getBrightnessCap(), 0f); + assertTrue(throttler.isThrottled()); + assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL, + throttler.getBrightnessMaxReason()); + + // Set status more than high enough to trigger throttling + listener.notifyThrottling(getSkinTemp(level.thermalStatus + 1)); + mTestLooper.dispatchAll(); + assertEquals(level.brightness, throttler.getBrightnessCap(), 0f); + assertTrue(throttler.isThrottled()); + assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL, + throttler.getBrightnessMaxReason()); + + // Return to the lower throttling level + listener.notifyThrottling(getSkinTemp(level.thermalStatus)); + mTestLooper.dispatchAll(); + assertEquals(level.brightness, throttler.getBrightnessCap(), 0f); + assertTrue(throttler.isThrottled()); + assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL, + throttler.getBrightnessMaxReason()); + + // Cool down + listener.notifyThrottling(getSkinTemp(level.thermalStatus - 1)); + mTestLooper.dispatchAll(); + assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f); + assertFalse(throttler.isThrottled()); + assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE, + throttler.getBrightnessMaxReason()); + } + + @Test + public void testThrottlingMultiLevel() throws Exception { + final ThrottlingLevel levelLo = new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, + 0.62f); + final ThrottlingLevel levelHi = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, + 0.25f); + + List levels = new ArrayList<>(); + levels.add(levelLo); + levels.add(levelHi); + final BrightnessThrottlingData data = BrightnessThrottlingData.create(levels); + final BrightnessThrottler throttler = createThrottlerSupported(data); + assertTrue(throttler.deviceSupportsThrottling()); + + verify(mThermalServiceMock).registerThermalEventListenerWithType( + mThermalEventListenerCaptor.capture(), eq(Temperature.TYPE_SKIN)); + final IThermalEventListener listener = mThermalEventListenerCaptor.getValue(); + + // Set status too low to trigger throttling + listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus - 1)); + mTestLooper.dispatchAll(); + assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f); + assertFalse(throttler.isThrottled()); + assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE, throttler.getBrightnessMaxReason()); + + // Set status just high enough to trigger throttling + listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus)); + mTestLooper.dispatchAll(); + assertEquals(levelLo.brightness, throttler.getBrightnessCap(), 0f); + assertTrue(throttler.isThrottled()); + assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL, + throttler.getBrightnessMaxReason()); + + // Set status to an intermediate throttling level + listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus + 1)); + mTestLooper.dispatchAll(); + assertEquals(levelLo.brightness, throttler.getBrightnessCap(), 0f); + assertTrue(throttler.isThrottled()); + assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL, + throttler.getBrightnessMaxReason()); + + // Set status to the highest configured throttling level + listener.notifyThrottling(getSkinTemp(levelHi.thermalStatus)); + mTestLooper.dispatchAll(); + assertEquals(levelHi.brightness, throttler.getBrightnessCap(), 0f); + assertTrue(throttler.isThrottled()); + assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL, + throttler.getBrightnessMaxReason()); + + // Set status to exceed the highest configured throttling level + listener.notifyThrottling(getSkinTemp(levelHi.thermalStatus + 1)); + mTestLooper.dispatchAll(); + assertEquals(levelHi.brightness, throttler.getBrightnessCap(), 0f); + assertTrue(throttler.isThrottled()); + assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL, + throttler.getBrightnessMaxReason()); + + // Return to an intermediate throttling level + listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus + 1)); + mTestLooper.dispatchAll(); + assertEquals(levelLo.brightness, throttler.getBrightnessCap(), 0f); + assertTrue(throttler.isThrottled()); + assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL, + throttler.getBrightnessMaxReason()); + + // Return to the lowest configured throttling level + listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus)); + mTestLooper.dispatchAll(); + assertEquals(levelLo.brightness, throttler.getBrightnessCap(), 0f); + assertTrue(throttler.isThrottled()); + assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL, + throttler.getBrightnessMaxReason()); + + // Cool down + listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus - 1)); + mTestLooper.dispatchAll(); + assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f); + assertFalse(throttler.isThrottled()); + assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE, throttler.getBrightnessMaxReason()); + } + private void assertThrottlingLevelsEquals( List expected, List actual) { @@ -115,4 +306,16 @@ public class BrightnessThrottlerTest { } } + private BrightnessThrottler createThrottlerUnsupported() { + return new BrightnessThrottler(mInjectorMock, mHandler, null, () -> {}); + } + + private BrightnessThrottler createThrottlerSupported(BrightnessThrottlingData data) { + assertNotNull(data); + return new BrightnessThrottler(mInjectorMock, mHandler, data, () -> {}); + } + + private Temperature getSkinTemp(@ThrottlingStatus int status) { + return new Temperature(30.0f, Temperature.TYPE_SKIN, "test_skin_temp", status); + } } diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java index 418831f47c1ae..40c039210939a 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java +++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java @@ -1461,7 +1461,8 @@ public class DisplayModeDirectorTest { // Turn on HBM, with brightness in the HBM range when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn( new BrightnessInfo(TRANSITION_POINT + FLOAT_TOLERANCE, 0.0f, 1.0f, - BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, TRANSITION_POINT)); + BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, TRANSITION_POINT, + BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE)); listener.onDisplayChanged(DISPLAY_ID); vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); assertVoteForRefreshRate(vote, hbmRefreshRate); @@ -1469,7 +1470,8 @@ public class DisplayModeDirectorTest { // Turn on HBM, with brightness below the HBM range when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn( new BrightnessInfo(TRANSITION_POINT - FLOAT_TOLERANCE, 0.0f, 1.0f, - BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, TRANSITION_POINT)); + BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, TRANSITION_POINT, + BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE)); listener.onDisplayChanged(DISPLAY_ID); vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); assertNull(vote); @@ -1477,7 +1479,8 @@ public class DisplayModeDirectorTest { // Turn off HBM when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn( new BrightnessInfo(0.45f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF, - TRANSITION_POINT)); + TRANSITION_POINT, + BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE)); listener.onDisplayChanged(DISPLAY_ID); vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); assertNull(vote); @@ -1485,7 +1488,8 @@ public class DisplayModeDirectorTest { // Turn on HBM, with brightness in the HBM range when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn( new BrightnessInfo(TRANSITION_POINT + FLOAT_TOLERANCE, 0.0f, 1.0f, - BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, TRANSITION_POINT)); + BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, TRANSITION_POINT, + BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE)); listener.onDisplayChanged(DISPLAY_ID); vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); assertVoteForRefreshRate(vote, hbmRefreshRate); @@ -1493,7 +1497,7 @@ public class DisplayModeDirectorTest { // Turn off HBM when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn( new BrightnessInfo(0.45f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF, - TRANSITION_POINT)); + TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE)); listener.onDisplayChanged(DISPLAY_ID); vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); assertNull(vote); @@ -1501,7 +1505,8 @@ public class DisplayModeDirectorTest { // Turn on HBM, with brightness below the HBM range when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn( new BrightnessInfo(TRANSITION_POINT - FLOAT_TOLERANCE, 0.0f, 1.0f, - BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, TRANSITION_POINT)); + BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, TRANSITION_POINT, + BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE)); listener.onDisplayChanged(DISPLAY_ID); vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); assertNull(vote); @@ -1509,7 +1514,7 @@ public class DisplayModeDirectorTest { // Turn off HBM when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn( new BrightnessInfo(0.45f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF, - TRANSITION_POINT)); + TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE)); listener.onDisplayChanged(DISPLAY_ID); vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); assertNull(vote); @@ -1580,7 +1585,7 @@ public class DisplayModeDirectorTest { // Turn on HBM when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn( new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT, - TRANSITION_POINT)); + TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE)); listener.onDisplayChanged(DISPLAY_ID); vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); assertVoteForRefreshRate(vote, initialRefreshRate); @@ -1598,7 +1603,7 @@ public class DisplayModeDirectorTest { // Turn off HBM when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn( new BrightnessInfo(0.43f, 0.1f, 0.8f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF, - TRANSITION_POINT)); + TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE)); listener.onDisplayChanged(DISPLAY_ID); vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); assertNull(vote); @@ -1606,7 +1611,7 @@ public class DisplayModeDirectorTest { // Turn HBM on again and ensure the updated vote value stuck when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn( new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT, - TRANSITION_POINT)); + TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE)); listener.onDisplayChanged(DISPLAY_ID); vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); assertVoteForRefreshRate(vote, updatedRefreshRate); @@ -1622,7 +1627,7 @@ public class DisplayModeDirectorTest { // Turn off HBM when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn( new BrightnessInfo(0.43f, 0.1f, 0.8f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF, - TRANSITION_POINT)); + TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE)); listener.onDisplayChanged(DISPLAY_ID); vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); assertNull(vote); @@ -1654,7 +1659,7 @@ public class DisplayModeDirectorTest { // Turn on HBM when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn( new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT, - TRANSITION_POINT)); + TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE)); listener.onDisplayChanged(DISPLAY_ID); vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); assertNull(vote); @@ -1662,7 +1667,7 @@ public class DisplayModeDirectorTest { // Turn off HBM when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn( new BrightnessInfo(0.43f, 0.1f, 0.8f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF, - TRANSITION_POINT)); + TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE)); listener.onDisplayChanged(DISPLAY_ID); vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); assertNull(vote); @@ -1694,7 +1699,7 @@ public class DisplayModeDirectorTest { // Turn on HBM when HBM is supported; expect a valid transition point and a vote. when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn( new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT, - TRANSITION_POINT)); + TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE)); listener.onDisplayChanged(DISPLAY_ID); vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); assertVoteForRefreshRate(vote, 60.0f); @@ -1702,7 +1707,7 @@ public class DisplayModeDirectorTest { // Turn off HBM when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn( new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF, - TRANSITION_POINT)); + TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE)); listener.onDisplayChanged(DISPLAY_ID); vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); assertNull(vote); @@ -1711,7 +1716,7 @@ public class DisplayModeDirectorTest { // no vote. when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn( new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT, - HBM_TRANSITION_POINT_INVALID)); + HBM_TRANSITION_POINT_INVALID, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE)); listener.onDisplayChanged(DISPLAY_ID); vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); assertNull(vote); @@ -1720,7 +1725,7 @@ public class DisplayModeDirectorTest { // no vote. when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn( new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, - HBM_TRANSITION_POINT_INVALID)); + HBM_TRANSITION_POINT_INVALID, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE)); listener.onDisplayChanged(DISPLAY_ID); vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); assertNull(vote); @@ -1728,7 +1733,7 @@ public class DisplayModeDirectorTest { // Turn off HBM when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn( new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF, - TRANSITION_POINT)); + TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE)); listener.onDisplayChanged(DISPLAY_ID); vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); assertNull(vote); @@ -1737,7 +1742,8 @@ public class DisplayModeDirectorTest { private void setHbmAndAssertRefreshRate( DisplayModeDirector director, DisplayListener listener, int mode, float rr) { when(mInjector.getBrightnessInfo(DISPLAY_ID)) - .thenReturn(new BrightnessInfo(1.0f, 0.0f, 1.0f, mode, TRANSITION_POINT)); + .thenReturn(new BrightnessInfo(1.0f, 0.0f, 1.0f, mode, TRANSITION_POINT, + BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE)); listener.onDisplayChanged(DISPLAY_ID); final Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); @@ -1817,7 +1823,7 @@ public class DisplayModeDirectorTest { // Turn on HBM when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn( new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT, - TRANSITION_POINT)); + TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE)); listener.onDisplayChanged(DISPLAY_ID); vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); assertVoteForRefreshRate(vote, 60.f); @@ -1978,7 +1984,8 @@ public class DisplayModeDirectorTest { when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn( new BrightnessInfo(floatBri, floatAdjBri, 0.0f, 1.0f, - BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF, TRANSITION_POINT)); + BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF, TRANSITION_POINT, + BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE)); listener.onDisplayChanged(DISPLAY_ID); } diff --git a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java index 67ac1dc188493..b7af010103bc2 100644 --- a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java @@ -16,6 +16,8 @@ package com.android.server.display; +import static android.hardware.display.BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE; +import static android.hardware.display.BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL; import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR; import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF; import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT; @@ -201,7 +203,7 @@ public class HighBrightnessModeControllerTest { hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED); hbmc.onAmbientLuxChange(MINIMUM_LUX + 1); - hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f); + hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f); // Verify we are in HBM assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT); @@ -233,7 +235,7 @@ public class HighBrightnessModeControllerTest { hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED); hbmc.onAmbientLuxChange(MINIMUM_LUX + 1); - hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f); + hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f); // Verify we are in HBM assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT); @@ -258,18 +260,18 @@ public class HighBrightnessModeControllerTest { hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED); hbmc.onAmbientLuxChange(MINIMUM_LUX + 1); - hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f); + hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f); advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS / 2); // Verify we are in HBM assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT); - hbmc.onBrightnessChanged(TRANSITION_POINT - 0.01f); + hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT - 0.01f); advanceTime(1); assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT); - hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f); + hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f); advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS / 2); assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT); @@ -288,13 +290,13 @@ public class HighBrightnessModeControllerTest { hbmc.onAmbientLuxChange(MINIMUM_LUX + 1); // Go into HBM for half the allowed window - hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f); + hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f); advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS / 2); assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT); // Move lux below threshold (ending first event); hbmc.onAmbientLuxChange(MINIMUM_LUX - 1); - hbmc.onBrightnessChanged(TRANSITION_POINT); + hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT); assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF); // Move up some amount of time so that there's still time in the window even after a @@ -304,7 +306,7 @@ public class HighBrightnessModeControllerTest { // Go into HBM for just under the second half of allowed window hbmc.onAmbientLuxChange(MINIMUM_LUX + 1); - hbmc.onBrightnessChanged(TRANSITION_POINT + 1); + hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 1); advanceTime((TIME_ALLOWED_IN_WINDOW_MILLIS / 2) - 1); assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT); @@ -434,7 +436,7 @@ public class HighBrightnessModeControllerTest { float brightness = 0.5f; float expectedHdrBrightness = MathUtils.map(DEFAULT_MIN, TRANSITION_POINT, DEFAULT_MIN, DEFAULT_MAX, brightness); // map value from normal range to hdr range - hbmc.onBrightnessChanged(brightness); + hbmcOnBrightnessChanged(hbmc, brightness); advanceTime(0); assertEquals(expectedHdrBrightness, hbmc.getHdrBrightnessValue(), EPSILON); @@ -442,21 +444,21 @@ public class HighBrightnessModeControllerTest { brightness = 0.33f; expectedHdrBrightness = MathUtils.map(DEFAULT_MIN, TRANSITION_POINT, DEFAULT_MIN, DEFAULT_MAX, brightness); // map value from normal range to hdr range - hbmc.onBrightnessChanged(brightness); + hbmcOnBrightnessChanged(hbmc, brightness); advanceTime(0); assertEquals(expectedHdrBrightness, hbmc.getHdrBrightnessValue(), EPSILON); // Try the min value brightness = DEFAULT_MIN; expectedHdrBrightness = DEFAULT_MIN; - hbmc.onBrightnessChanged(brightness); + hbmcOnBrightnessChanged(hbmc, brightness); advanceTime(0); assertEquals(expectedHdrBrightness, hbmc.getHdrBrightnessValue(), EPSILON); // Try the max value brightness = TRANSITION_POINT; expectedHdrBrightness = DEFAULT_MAX; - hbmc.onBrightnessChanged(brightness); + hbmcOnBrightnessChanged(hbmc, brightness); advanceTime(0); assertEquals(expectedHdrBrightness, hbmc.getHdrBrightnessValue(), EPSILON); } @@ -467,7 +469,7 @@ public class HighBrightnessModeControllerTest { final int displayStatsId = mDisplayUniqueId.hashCode(); hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED); - hbmc.onBrightnessChanged(TRANSITION_POINT); + hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT); hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/, DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/); advanceTime(0); @@ -489,7 +491,7 @@ public class HighBrightnessModeControllerTest { hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED); hbmc.onAmbientLuxChange(MINIMUM_LUX + 1); - hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f); + hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f); // Verify Stats HBM_ON_SUNLIGHT verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId), @@ -506,12 +508,12 @@ public class HighBrightnessModeControllerTest { } @Test - public void tetHbmStats_NbmHdrNoReport() { + public void testHbmStats_NbmHdrNoReport() { final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock()); final int displayStatsId = mDisplayUniqueId.hashCode(); hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED); - hbmc.onBrightnessChanged(DEFAULT_MIN); + hbmcOnBrightnessChanged(hbmc, DEFAULT_MIN); hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/, DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/); advanceTime(0); @@ -524,13 +526,13 @@ public class HighBrightnessModeControllerTest { } @Test - public void tetHbmStats_HighLuxLowBrightnessNoReport() { + public void testHbmStats_HighLuxLowBrightnessNoReport() { final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock()); final int displayStatsId = mDisplayUniqueId.hashCode(); hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED); hbmc.onAmbientLuxChange(MINIMUM_LUX + 1); - hbmc.onBrightnessChanged(DEFAULT_MIN); + hbmcOnBrightnessChanged(hbmc, DEFAULT_MIN); advanceTime(0); // verify in HBM sunlight mode assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode()); @@ -541,8 +543,10 @@ public class HighBrightnessModeControllerTest { anyInt()); } + // Test reporting of thermal throttling when triggered by HighBrightnessModeController's + // internal thermal throttling. @Test - public void testHbmStats_ThermalOff() throws Exception { + public void testHbmStats_InternalThermalOff() throws Exception { final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock()); final int displayStatsId = mDisplayUniqueId.hashCode(); @@ -552,7 +556,7 @@ public class HighBrightnessModeControllerTest { hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED); hbmc.onAmbientLuxChange(MINIMUM_LUX + 1); - hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f); + hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f); advanceTime(1); verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId), eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT), @@ -566,6 +570,37 @@ public class HighBrightnessModeControllerTest { eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_THERMAL_LIMIT)); } + // Test reporting of thermal throttling when triggered externally through + // HighBrightnessModeController.onBrightnessChanged() + @Test + public void testHbmStats_ExternalThermalOff() throws Exception { + final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock()); + final int displayStatsId = mDisplayUniqueId.hashCode(); + final float hbmBrightness = TRANSITION_POINT + 0.01f; + final float nbmBrightness = TRANSITION_POINT - 0.01f; + + hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED); + hbmc.onAmbientLuxChange(MINIMUM_LUX + 1); + // Brightness is unthrottled, HBM brightness granted + hbmc.onBrightnessChanged(hbmBrightness, hbmBrightness, BRIGHTNESS_MAX_REASON_NONE); + advanceTime(1); + verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId), + eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT), + eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN)); + + // Brightness is thermally throttled, HBM brightness denied (NBM brightness granted) + hbmc.onBrightnessChanged(nbmBrightness, hbmBrightness, BRIGHTNESS_MAX_REASON_THERMAL); + advanceTime(1); + // We expect HBM mode to remain set to sunlight, indicating that HBMC *allows* this mode. + // However, we expect the HBM state reported by HBMC to be off, since external thermal + // throttling (reported to HBMC through onBrightnessChanged()) lowers brightness to below + // the HBM transition point. + assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode()); + verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId), + eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF), + eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_THERMAL_LIMIT)); + } + @Test public void testHbmStats_TimeOut() { final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock()); @@ -573,7 +608,7 @@ public class HighBrightnessModeControllerTest { hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED); hbmc.onAmbientLuxChange(MINIMUM_LUX + 1); - hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f); + hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f); advanceTime(0); verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId), eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT), @@ -594,7 +629,7 @@ public class HighBrightnessModeControllerTest { hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED); hbmc.onAmbientLuxChange(MINIMUM_LUX + 1); - hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f); + hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f); advanceTime(0); verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId), eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT), @@ -613,7 +648,7 @@ public class HighBrightnessModeControllerTest { hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED); hbmc.onAmbientLuxChange(MINIMUM_LUX + 1); - hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f); + hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f); advanceTime(0); verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId), eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT), @@ -667,4 +702,8 @@ public class HighBrightnessModeControllerTest { private Temperature getSkinTemp(@ThrottlingStatus int status) { return new Temperature(30.0f, Temperature.TYPE_SKIN, "test_skin_temp", status); } + + private void hbmcOnBrightnessChanged(HighBrightnessModeController hbmc, float brightness) { + hbmc.onBrightnessChanged(brightness, brightness, BRIGHTNESS_MAX_REASON_NONE); + } }