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/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index f3969b19e11f5..a4a6eb4312cca 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -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 points = map.getBrightnessThrottlingPoint(); + // At least 1 point is guaranteed by the display device config schema + List 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 throttlingLevels; + + static public BrightnessThrottlingData create(List 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 inLevels) { + throttlingLevels = new ArrayList<>(inLevels.size()); + for (ThrottlingLevel level : inLevels) { + throttlingLevels.add(new ThrottlingLevel(level.thermalStatus, level.brightness)); + } + } + + } } 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 b3be894b9510e..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); @@ -446,31 +462,44 @@ class HighBrightnessModeController { private void updateHbmMode() { int newHbmMode = calculateHighBrightnessMode(); - updateHbmStats(mHbmMode, newHbmMode); + updateHbmStats(newHbmMode); if (mHbmMode != newHbmMode) { mHbmMode = newHbmMode; mHbmChangeCallback.run(); } } - private void updateHbmStats(int mode, int newMode) { + 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) { + } else if (newMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT + && mBrightness > transitionPoint) { state = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT; } if (state == mHbmStatsState) { return; } - mHbmStatsState = state; int reason = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN; - boolean oldHbmSv = (mode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT); - boolean newHbmSv = (newMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT); + final boolean oldHbmSv = (mHbmStatsState + == FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT); + 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) { @@ -483,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) { @@ -496,6 +525,7 @@ class HighBrightnessModeController { } mInjector.reportHbmStateChange(mDisplayStatsId, state, reason); + mHbmStatsState = state; } private String hbmStatsStateToString(int hbmStatsState) { @@ -572,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/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd index 574dbfd1046dd..79d80366efd38 100644 --- a/services/core/xsd/display-device-config/display-device-config.xsd +++ b/services/core/xsd/display-device-config/display-device-config.xsd @@ -38,6 +38,10 @@ + + + + @@ -154,6 +158,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt index 04f0916dbc549..0b7df4d0bc7cf 100644 --- a/services/core/xsd/display-device-config/schema/current.txt +++ b/services/core/xsd/display-device-config/schema/current.txt @@ -7,6 +7,19 @@ package com.android.server.display.config { method public final void setMinimum(@NonNull java.math.BigDecimal); } + public class BrightnessThrottlingMap { + ctor public BrightnessThrottlingMap(); + method @NonNull public final java.util.List getBrightnessThrottlingPoint(); + } + + public class BrightnessThrottlingPoint { + ctor public BrightnessThrottlingPoint(); + method @NonNull public final java.math.BigDecimal getBrightness(); + method @NonNull public final com.android.server.display.config.ThermalStatus getThermalStatus(); + method public final void setBrightness(@NonNull java.math.BigDecimal); + method public final void setThermalStatus(@NonNull com.android.server.display.config.ThermalStatus); + } + public class Density { ctor public Density(); method @NonNull public final java.math.BigInteger getDensity(); @@ -39,6 +52,7 @@ package com.android.server.display.config { method public final java.math.BigDecimal getScreenBrightnessRampFastIncrease(); method public final java.math.BigDecimal getScreenBrightnessRampSlowDecrease(); method public final java.math.BigDecimal getScreenBrightnessRampSlowIncrease(); + method @NonNull public final com.android.server.display.config.ThermalThrottling getThermalThrottling(); method public final void setAmbientBrightnessChangeThresholds(@NonNull com.android.server.display.config.Thresholds); method public final void setAmbientLightHorizonLong(java.math.BigInteger); method public final void setAmbientLightHorizonShort(java.math.BigInteger); @@ -54,6 +68,7 @@ package com.android.server.display.config { method public final void setScreenBrightnessRampFastIncrease(java.math.BigDecimal); method public final void setScreenBrightnessRampSlowDecrease(java.math.BigDecimal); method public final void setScreenBrightnessRampSlowIncrease(java.math.BigDecimal); + method public final void setThermalThrottling(@NonNull com.android.server.display.config.ThermalThrottling); } public class DisplayQuirks { @@ -131,6 +146,12 @@ package com.android.server.display.config { enum_constant public static final com.android.server.display.config.ThermalStatus shutdown; } + public class ThermalThrottling { + ctor public ThermalThrottling(); + method @NonNull public final com.android.server.display.config.BrightnessThrottlingMap getBrightnessThrottlingMap(); + method public final void setBrightnessThrottlingMap(@NonNull com.android.server.display.config.BrightnessThrottlingMap); + } + public class Thresholds { ctor public Thresholds(); method @NonNull public final com.android.server.display.config.BrightnessThresholds getBrighteningThresholds(); diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java new file mode 100644 index 0000000000000..0ed90d27db996 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java @@ -0,0 +1,321 @@ +/* + * 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 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; +import java.util.Arrays; +import java.util.List; + +@SmallTest +@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; + } + }); + + } + + ///////////////// + // Test Methods + ///////////////// + + @Test + public void testBrightnessThrottlingData() { + List singleLevel = new ArrayList<>(); + singleLevel.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.25f)); + + List validLevels = new ArrayList<>(); + validLevels.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.62f)); + validLevels.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 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 unsortedBrightnessLevels = new ArrayList<>(); + unsortedBrightnessLevels.add( + new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.25f)); + unsortedBrightnessLevels.add( + new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 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; + data = BrightnessThrottlingData.create((List)null); + assertEquals(data, null); + data = BrightnessThrottlingData.create((BrightnessThrottlingData)null); + assertEquals(data, null); + data = BrightnessThrottlingData.create(new ArrayList()); + assertEquals(data, null); + data = BrightnessThrottlingData.create(unsortedThermalLevels); + assertEquals(data, null); + data = BrightnessThrottlingData.create(unsortedBrightnessLevels); + assertEquals(data, null); + data = BrightnessThrottlingData.create(unsortedLevels); + assertEquals(data, null); + data = BrightnessThrottlingData.create(invalidLevel); + assertEquals(data, null); + + // Test valid data + data = BrightnessThrottlingData.create(singleLevel); + assertNotEquals(data, null); + assertThrottlingLevelsEquals(singleLevel, data.throttlingLevels); + + data = BrightnessThrottlingData.create(validLevels); + assertNotEquals(data, null); + 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) { + assertEquals(expected.size(), actual.size()); + + for (int i = 0; i < expected.size(); i++) { + ThrottlingLevel expectedLevel = expected.get(i); + ThrottlingLevel actualLevel = actual.get(i); + + assertEquals(expectedLevel.thermalStatus, actualLevel.thermalStatus); + assertEquals(expectedLevel.brightness, actualLevel.brightness, 0.0f); + } + } + + 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 4bb5d7482e25a..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,7 +526,27 @@ public class HighBrightnessModeControllerTest { } @Test - public void testHbmStats_ThermalOff() throws Exception { + 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); + hbmcOnBrightnessChanged(hbmc, DEFAULT_MIN); + advanceTime(0); + // verify in HBM sunlight mode + assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode()); + + // Verify Stats HBM_ON_SUNLIGHT not report + verify(mInjectorMock, never()).reportHbmStateChange(eq(displayStatsId), + eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT), + anyInt()); + } + + // Test reporting of thermal throttling when triggered by HighBrightnessModeController's + // internal thermal throttling. + @Test + public void testHbmStats_InternalThermalOff() throws Exception { final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock()); final int displayStatsId = mDisplayUniqueId.hashCode(); @@ -534,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), @@ -548,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()); @@ -555,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), @@ -576,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), @@ -595,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), @@ -649,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); + } }