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

Commit 5ba3e1fe authored by Santos Cordon's avatar Santos Cordon
Browse files

Add limitations to HDR.

1. Only allow HDR if HDR takes up more than 50% of the screen.
2. Scale normal brightness range to HDR levels. To do this, we now keep
the normal brightness range when HDR is enabled and scale the brightness
value to the higher HBM range. This allows HDR to scale as it did
previous as well as function as expected when automatic-brightness is
off.
3. Add more logging to HBMController dumpsys.

Bug: 187804126
Bug: 188479946
Bug: 189384189
Test: atest com.android.server.display
Change-Id: I515a9ee7ca16bcf8070b1cdab09943b68f038949
parent 5f5e03d3
Loading
Loading
Loading
Loading
+0 −1
Original line number Diff line number Diff line
@@ -778,7 +778,6 @@ class AutomaticBrightnessController {
                    mScreenBrightnessThresholds.getBrighteningThreshold(newScreenAutoBrightness));
            mScreenDarkeningThreshold = clampScreenBrightness(
                    mScreenBrightnessThresholds.getDarkeningThreshold(newScreenAutoBrightness));
            mHbmController.onAutoBrightnessChanged(mScreenAutoBrightness);

            if (sendUpdate) {
                mCallbacks.updateBrightness();
+5 −5
Original line number Diff line number Diff line
@@ -1281,6 +1281,11 @@ public final class DisplayManagerService extends SystemService {
        sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
        scheduleTraversalLocked(false);
        mPersistentDataStore.saveIfNeeded();

        DisplayPowerController dpc = mDisplayPowerControllers.get(displayId);
        if (dpc != null) {
            dpc.onDisplayChanged();
        }
    }

    private void handleLogicalDisplayFrameRateOverridesChangedLocked(
@@ -1312,11 +1317,6 @@ public final class DisplayManagerService extends SystemService {
        if (work != null) {
            mHandler.post(work);
        }
        final int displayId = display.getDisplayIdLocked();
        DisplayPowerController dpc = mDisplayPowerControllers.get(displayId);
        if (dpc != null) {
            dpc.onDisplayChanged();
        }
        handleLogicalDisplayChangedLocked(display);
    }

+14 −10
Original line number Diff line number Diff line
@@ -511,7 +511,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
        mSkipScreenOnBrightnessRamp = resources.getBoolean(
                com.android.internal.R.bool.config_skipScreenOnBrightnessRamp);

        mHbmController = createHbmController();
        mHbmController = createHbmControllerLocked();

        // Seed the cached brightness
        saveBrightnessInfo(getScreenBrightnessSetting());
@@ -717,6 +717,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
        final String uniqueId = device.getUniqueId();
        final DisplayDeviceConfig config = device.getDisplayDeviceConfig();
        final IBinder token = device.getDisplayTokenLocked();
        final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
        mHandler.post(() -> {
            if (mDisplayDevice == device) {
                return;
@@ -724,7 +725,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
            mDisplayDevice = device;
            mUniqueDisplayId = uniqueId;
            mDisplayDeviceConfig = config;
            loadFromDisplayDeviceConfig(token);
            loadFromDisplayDeviceConfig(token, info);
        });
    }

@@ -765,7 +766,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
        }
    }

    private void loadFromDisplayDeviceConfig(IBinder token) {
    private void loadFromDisplayDeviceConfig(IBinder token, DisplayDeviceInfo info) {
        // All properties that depend on the associated DisplayDevice and the DDC must be
        // updated here.
        loadAmbientLightSensor();
@@ -774,7 +775,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
        loadNitsRange(mContext.getResources());
        setUpAutoBrightness(mContext.getResources(), mHandler);
        reloadReduceBrightColours();
        mHbmController.resetHbmData(token, mDisplayDeviceConfig.getHighBrightnessModeData());
        mHbmController.resetHbmData(info.width, info.height, token,
                mDisplayDeviceConfig.getHighBrightnessModeData(), mBrightnessSetting);
    }

    private void sendUpdatePowerState() {
@@ -1343,6 +1345,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
            if (mHbmController.getHighBrightnessMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR
                    && ((mBrightnessReason.modifier & BrightnessReason.MODIFIER_DIMMED) == 0
                    || (mBrightnessReason.modifier & BrightnessReason.MODIFIER_LOW_POWER) == 0)) {
                // We want to scale HDR brightness level with the SDR level
                animateValue = mHbmController.getHdrBrightnessValue();
            }

@@ -1514,21 +1517,22 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
        }
    }

    private HighBrightnessModeController createHbmController() {
        final DisplayDeviceConfig ddConfig =
                mLogicalDisplay.getPrimaryDisplayDeviceLocked().getDisplayDeviceConfig();
    private HighBrightnessModeController createHbmControllerLocked() {
        final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
        final DisplayDeviceConfig ddConfig = device.getDisplayDeviceConfig();
        final IBinder displayToken =
                mLogicalDisplay.getPrimaryDisplayDeviceLocked().getDisplayTokenLocked();
        final DisplayDeviceConfig.HighBrightnessModeData hbmData =
                ddConfig != null ? ddConfig.getHighBrightnessModeData() : null;
        return new HighBrightnessModeController(mHandler, displayToken, PowerManager.BRIGHTNESS_MIN,
                PowerManager.BRIGHTNESS_MAX, hbmData,
        final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
        return new HighBrightnessModeController(mHandler, info.width, info.height, displayToken,
                PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, hbmData,
                () -> {
                    sendUpdatePowerStateLocked();
                    mHandler.post(mOnBrightnessChangeRunnable);
                    // TODO(b/192258832): Switch the HBMChangeCallback to a listener pattern.
                    mAutomaticBrightnessController.update();
                }, mContext);
                }, mContext, mBrightnessSetting);
    }

    private void blockScreenOn() {
+80 −34
Original line number Diff line number Diff line
@@ -31,11 +31,13 @@ import android.os.SystemClock;
import android.os.Temperature;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.MathUtils;
import android.util.Slog;
import android.util.TimeUtils;
import android.view.SurfaceControlHdrLayerInfoListener;

import com.android.internal.annotations.VisibleForTesting;
import com.android.server.display.BrightnessSetting.BrightnessSettingListener;
import com.android.server.display.DisplayDeviceConfig.HighBrightnessModeData;
import com.android.server.display.DisplayManagerService.Clock;

@@ -56,6 +58,8 @@ class HighBrightnessModeController {

    private static final boolean DEBUG = false;

    private static final float HDR_PERCENT_OF_SCREEN_REQUIRED = 0.50f;

    private final float mBrightnessMin;
    private final float mBrightnessMax;
    private final Handler mHandler;
@@ -66,6 +70,7 @@ class HighBrightnessModeController {
    private final Context mContext;
    private final SettingsObserver mSettingsObserver;
    private final Injector mInjector;
    private final BrightnessSettingListener mBrightnessSettingListener = this::onBrightnessChanged;

    private SurfaceControlHdrLayerInfoListener mHdrListener;
    private HighBrightnessModeData mHbmData;
@@ -74,11 +79,15 @@ class HighBrightnessModeController {
    private boolean mIsInAllowedAmbientRange = false;
    private boolean mIsTimeAvailable = false;
    private boolean mIsAutoBrightnessEnabled = false;
    private float mAutoBrightness;
    private float mBrightness;
    private int mHbmMode = BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
    private boolean mIsHdrLayerPresent = false;
    private boolean mIsThermalStatusWithinLimit = true;
    private boolean mIsBlockedByLowPowerMode = false;
    private int mWidth;
    private int mHeight;
    private BrightnessSetting mBrightnessSetting;
    private float mAmbientLux;

    /**
     * If HBM is currently running, this is the start time for the current HBM session.
@@ -92,30 +101,32 @@ class HighBrightnessModeController {
     */
    private LinkedList<HbmEvent> mEvents = new LinkedList<>();

    HighBrightnessModeController(Handler handler, IBinder displayToken, float brightnessMin,
            float brightnessMax, HighBrightnessModeData hbmData, Runnable hbmChangeCallback,
            Context context) {
        this(new Injector(), handler, displayToken, brightnessMin, brightnessMax,
                hbmData, hbmChangeCallback, context);
    HighBrightnessModeController(Handler handler, int width, int height, IBinder displayToken,
            float brightnessMin, float brightnessMax, HighBrightnessModeData hbmData,
            Runnable hbmChangeCallback, Context context, BrightnessSetting brightnessSetting) {
        this(new Injector(), handler, width, height, displayToken, brightnessMin, brightnessMax,
                hbmData, hbmChangeCallback, context, brightnessSetting);
    }

    @VisibleForTesting
    HighBrightnessModeController(Injector injector, Handler handler, IBinder displayToken,
            float brightnessMin, float brightnessMax, HighBrightnessModeData hbmData,
            Runnable hbmChangeCallback, Context context) {
    HighBrightnessModeController(Injector injector, Handler handler, int width, int height,
            IBinder displayToken, float brightnessMin, float brightnessMax,
            HighBrightnessModeData hbmData, Runnable hbmChangeCallback,
            Context context, BrightnessSetting brightnessSetting) {
        mInjector = injector;
        mContext = context;
        mClock = injector.getClock();
        mHandler = handler;
        mBrightnessMin = brightnessMin;
        mBrightnessMax = brightnessMax;
        mBrightness = brightnessSetting.getBrightness();
        mHbmChangeCallback = hbmChangeCallback;
        mContext = context;
        mAutoBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
        mRecalcRunnable = this::recalculateTimeAllowance;
        mHdrListener = new HdrListener();
        mSkinThermalStatusObserver = new SkinThermalStatusObserver(mInjector, mHandler);
        mSettingsObserver = new SettingsObserver(mHandler);
        resetHbmData(displayToken, hbmData);
        mRecalcRunnable = this::recalculateTimeAllowance;
        mHdrListener = new HdrListener();

        resetHbmData(width, height, displayToken, hbmData, brightnessSetting);
    }

    void setAutoBrightnessEnabled(boolean isEnabled) {
@@ -123,7 +134,7 @@ class HighBrightnessModeController {
            return;
        }
        if (DEBUG) {
            Slog.d(TAG, "setAutoBrightness( " + isEnabled + " )");
            Slog.d(TAG, "setAutoBrightnessEnabled( " + isEnabled + " )");
        }
        mIsAutoBrightnessEnabled = isEnabled;
        mIsInAllowedAmbientRange = false; // reset when auto-brightness switches
@@ -147,11 +158,22 @@ class HighBrightnessModeController {
        }
    }

    float getNormalBrightnessMax() {
        return deviceSupportsHbm() ? mHbmData.transitionPoint : mBrightnessMax;
    }

    float getHdrBrightnessValue() {
        return mBrightnessMax;
        // For HDR brightness, we take the current brightness and scale it to the max. The reason
        // we do this is because we want brightness to go to HBM max when it would normally go
        // to normal max, meaning it should not wait to go to 10000 lux (or whatever the transition
        // point happens to be) in order to go full HDR. Likewise, HDR on manual brightness should
        // automatically scale the brightness without forcing the user to adjust to higher values.
        return MathUtils.map(getCurrentBrightnessMin(), getCurrentBrightnessMax(),
                mBrightnessMin, mBrightnessMax, mBrightness);
    }

    void onAmbientLuxChange(float ambientLux) {
        mAmbientLux = ambientLux;
        if (!deviceSupportsHbm() || !mIsAutoBrightnessEnabled) {
            return;
        }
@@ -163,17 +185,17 @@ class HighBrightnessModeController {
        }
    }

    void onAutoBrightnessChanged(float autoBrightness) {
    @VisibleForTesting
    void onBrightnessChanged(float brightness) {
        if (!deviceSupportsHbm()) {
            return;
        }
        final float oldAutoBrightness = mAutoBrightness;
        mAutoBrightness = autoBrightness;
        mBrightness = brightness;

        // If we are starting or ending a high brightness mode session, store the current
        // session in mRunningStartTimeMillis, or the old one in mEvents.
        final boolean wasHbmDrainingAvailableTime = mRunningStartTimeMillis != -1;
        final boolean shouldHbmDrainAvailableTime = mAutoBrightness > mHbmData.transitionPoint
        final boolean shouldHbmDrainAvailableTime = mBrightness > mHbmData.transitionPoint
                && !mIsHdrLayerPresent;
        if (wasHbmDrainingAvailableTime != shouldHbmDrainAvailableTime) {
            final long currentTime = mClock.uptimeMillis();
@@ -202,8 +224,12 @@ class HighBrightnessModeController {
        mSettingsObserver.stopObserving();
    }

    void resetHbmData(IBinder displayToken, HighBrightnessModeData hbmData) {
    void resetHbmData(int width, int height, IBinder displayToken, HighBrightnessModeData hbmData,
            BrightnessSetting brightnessSetting) {
        mWidth = width;
        mHeight = height;
        mHbmData = hbmData;
        resetBrightnessSetting(brightnessSetting);
        unregisterHdrListener();
        mSkinThermalStatusObserver.stopObserving();
        mSettingsObserver.stopObserving();
@@ -227,21 +253,23 @@ class HighBrightnessModeController {

    private void dumpLocal(PrintWriter pw) {
        pw.println("HighBrightnessModeController:");
        pw.println("  mBrightness=" + mBrightness);
        pw.println("  mCurrentMin=" + getCurrentBrightnessMin());
        pw.println("  mCurrentMax=" + getCurrentBrightnessMax());
        pw.println("  mHbmMode=" + BrightnessInfo.hbmToString(mHbmMode));
        pw.println("  remainingTime=" + calculateRemainingTime(mClock.uptimeMillis()));
        pw.println("  mHbmData=" + mHbmData);
        pw.println("  mAmbientLux=" + mAmbientLux);
        pw.println("  mIsInAllowedAmbientRange=" + mIsInAllowedAmbientRange);
        pw.println("  mIsTimeAvailable= " + mIsTimeAvailable);
        pw.println("  mIsAutoBrightnessEnabled=" + mIsAutoBrightnessEnabled);
        pw.println("  mAutoBrightness=" + mAutoBrightness);
        pw.println("  mIsHdrLayerPresent=" + mIsHdrLayerPresent);
        pw.println("  mBrightnessMin=" + mBrightnessMin);
        pw.println("  mBrightnessMax=" + mBrightnessMax);
        pw.println("  remainingTime=" + calculateRemainingTime(mClock.uptimeMillis()));
        pw.println("  mIsTimeAvailable= " + mIsTimeAvailable);
        pw.println("  mRunningStartTimeMillis=" + TimeUtils.formatUptime(mRunningStartTimeMillis));
        pw.println("  mIsThermalStatusWithinLimit=" + mIsThermalStatusWithinLimit);
        pw.println("  mIsBlockedByLowPowerMode=" + mIsBlockedByLowPowerMode);
        pw.println("  width*height=" + mWidth + "*" + mHeight);
        pw.println("  mEvents=");
        final long currentTime = mClock.uptimeMillis();
        long lastStartTime = currentTime;
@@ -268,9 +296,26 @@ class HighBrightnessModeController {
        return event.startTimeMillis;
    }

    private void resetBrightnessSetting(BrightnessSetting brightnessSetting) {
        if (mBrightnessSetting != null) {
            mBrightnessSetting.unregisterListener(mBrightnessSettingListener);
        }
        mBrightnessSetting = brightnessSetting;
        if (mBrightnessSetting != null) {
            mBrightnessSetting.registerListener(mBrightnessSettingListener);
        }
    }

    private boolean isCurrentlyAllowed() {
        return mIsHdrLayerPresent
                || (mIsAutoBrightnessEnabled && mIsTimeAvailable && mIsInAllowedAmbientRange
        // Returns true if HBM is allowed (above the ambient lux threshold) and there's still
        // time within the current window for additional HBM usage. We return false if there is an
        // HDR layer because we don't want the brightness MAX to change for HDR, which has its
        // brightness scaled in a different way than sunlight HBM that doesn't require changing
        // the MAX. HDR also needs to work under manual brightness which never adjusts the
        // brightness maximum; so we implement HDR-HBM in a way that doesn't adjust the max.
        // See {@link #getHdrBrightnessValue}.
        return !mIsHdrLayerPresent
                && (mIsAutoBrightnessEnabled && mIsTimeAvailable && mIsInAllowedAmbientRange
                && mIsThermalStatusWithinLimit && !mIsBlockedByLowPowerMode);
    }

@@ -334,13 +379,13 @@ class HighBrightnessModeController {
        // or if brightness is already in the high range, if there is any time left at all.
        final boolean isAllowedWithoutRestrictions = remainingTime >= mHbmData.timeMinMillis;
        final boolean isOnlyAllowedToStayOn = !isAllowedWithoutRestrictions
                && remainingTime > 0 && mAutoBrightness > mHbmData.transitionPoint;
                && remainingTime > 0 && mBrightness > mHbmData.transitionPoint;
        mIsTimeAvailable = isAllowedWithoutRestrictions || isOnlyAllowedToStayOn;

        // Calculate the time at which we want to recalculate mIsTimeAvailable in case a lux or
        // brightness change doesn't happen before then.
        long nextTimeout = -1;
        if (mAutoBrightness > mHbmData.transitionPoint) {
        if (mBrightness > mHbmData.transitionPoint) {
            // if we're in high-lux now, timeout when we run out of allowed time.
            nextTimeout = currentTime + remainingTime;
        } else if (!mIsTimeAvailable && mEvents.size() > 0) {
@@ -370,7 +415,7 @@ class HighBrightnessModeController {
                    + ", mIsInAllowedAmbientRange: " + mIsInAllowedAmbientRange
                    + ", mIsThermalStatusWithinLimit: " + mIsThermalStatusWithinLimit
                    + ", mIsBlockedByLowPowerMode: " + mIsBlockedByLowPowerMode
                    + ", brightness: " + mAutoBrightness
                    + ", mBrightness: " + mBrightness
                    + ", RunningStartTimeMillis: " + mRunningStartTimeMillis
                    + ", nextTimeout: " + (nextTimeout != -1 ? (nextTimeout - currentTime) : -1)
                    + ", events: " + mEvents);
@@ -447,11 +492,12 @@ class HighBrightnessModeController {
        public void onHdrInfoChanged(IBinder displayToken, int numberOfHdrLayers,
                int maxW, int maxH, int flags) {
            mHandler.post(() -> {
                mIsHdrLayerPresent = numberOfHdrLayers > 0;
                // Calling the auto-brightness update so that we can recalculate
                // auto-brightness with HDR in mind. When HDR layers are present,
                // we don't limit auto-brightness' HBM time limits.
                onAutoBrightnessChanged(mAutoBrightness);
                mIsHdrLayerPresent = numberOfHdrLayers > 0
                        && (float) (maxW * maxH)
                                >= ((float) (mWidth * mHeight) * HDR_PERCENT_OF_SCREEN_REQUIRED);
                // Calling the brightness update so that we can recalculate
                // brightness with HDR in mind.
                onBrightnessChanged(mBrightness);
            });
        }
    }
+22 −14
Original line number Diff line number Diff line
@@ -74,6 +74,9 @@ public class HighBrightnessModeControllerTest {
    private static final float DEFAULT_MIN = 0.01f;
    private static final float DEFAULT_MAX = 0.80f;

    private static final int DISPLAY_WIDTH = 900;
    private static final int DISPLAY_HEIGHT = 1600;

    private static final float EPSILON = 0.000001f;

    private OffsettableClock mClock;
@@ -90,6 +93,8 @@ public class HighBrightnessModeControllerTest {

    @Captor ArgumentCaptor<IThermalEventListener> mThermalEventListenerCaptor;

    @Mock private BrightnessSetting mBrightnessSetting;

    private static final HighBrightnessModeData DEFAULT_HBM_DATA =
            new HighBrightnessModeData(MINIMUM_LUX, TRANSITION_POINT, TIME_WINDOW_MILLIS,
                    TIME_ALLOWED_IN_WINDOW_MILLIS, TIME_MINIMUM_AVAILABLE_TO_ENABLE_MILLIS,
@@ -98,6 +103,8 @@ public class HighBrightnessModeControllerTest {
    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        mClock = new OffsettableClock.Stopped();
        mTestLooper = new TestLooper(mClock::now);
        mDisplayToken = null;
        mContextSpy = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
        final MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContextSpy);
@@ -114,8 +121,8 @@ public class HighBrightnessModeControllerTest {
    public void testNoHbmData() {
        initHandler(null);
        final HighBrightnessModeController hbmc = new HighBrightnessModeController(
                mInjectorMock, mHandler, mDisplayToken, DEFAULT_MIN, DEFAULT_MAX, null,
                () -> {}, mContextSpy);
                mInjectorMock, mHandler, DISPLAY_WIDTH, DISPLAY_HEIGHT, mDisplayToken, DEFAULT_MIN,
                DEFAULT_MAX, null, () -> {}, mContextSpy, mBrightnessSetting);
        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_OFF);
    }

@@ -123,8 +130,8 @@ public class HighBrightnessModeControllerTest {
    public void testNoHbmData_Enabled() {
        initHandler(null);
        final HighBrightnessModeController hbmc = new HighBrightnessModeController(
                mInjectorMock, mHandler, mDisplayToken, DEFAULT_MIN, DEFAULT_MAX, null,
                () -> {}, mContextSpy);
                mInjectorMock, mHandler, DISPLAY_WIDTH, DISPLAY_HEIGHT, mDisplayToken, DEFAULT_MIN,
                DEFAULT_MAX, null, () -> {}, mContextSpy, mBrightnessSetting);
        hbmc.setAutoBrightnessEnabled(true);
        hbmc.onAmbientLuxChange(MINIMUM_LUX - 1); // below allowed range
        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_OFF);
@@ -180,7 +187,7 @@ public class HighBrightnessModeControllerTest {

        hbmc.setAutoBrightnessEnabled(true);
        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
        hbmc.onAutoBrightnessChanged(TRANSITION_POINT + 0.01f);
        hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f);

        // Verify we are in HBM
        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
@@ -212,7 +219,7 @@ public class HighBrightnessModeControllerTest {

        hbmc.setAutoBrightnessEnabled(true);
        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
        hbmc.onAutoBrightnessChanged(TRANSITION_POINT + 0.01f);
        hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f);

        // Verify we are in HBM
        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
@@ -237,18 +244,18 @@ public class HighBrightnessModeControllerTest {
        hbmc.setAutoBrightnessEnabled(true);
        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);

        hbmc.onAutoBrightnessChanged(TRANSITION_POINT + 0.01f);
        hbmc.onBrightnessChanged(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.onAutoBrightnessChanged(TRANSITION_POINT - 0.01f);
        hbmc.onBrightnessChanged(TRANSITION_POINT - 0.01f);
        advanceTime(1);

        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);

        hbmc.onAutoBrightnessChanged(TRANSITION_POINT + 0.01f);
        hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f);
        advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS / 2);

        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
@@ -267,13 +274,13 @@ public class HighBrightnessModeControllerTest {
        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);

        // Go into HBM for half the allowed window
        hbmc.onAutoBrightnessChanged(TRANSITION_POINT + 0.01f);
        hbmc.onBrightnessChanged(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.onAutoBrightnessChanged(TRANSITION_POINT);
        hbmc.onBrightnessChanged(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
@@ -283,7 +290,7 @@ public class HighBrightnessModeControllerTest {

        // Go into HBM for just under the second half of allowed window
        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
        hbmc.onAutoBrightnessChanged(TRANSITION_POINT + 1);
        hbmc.onBrightnessChanged(TRANSITION_POINT + 1);
        advanceTime((TIME_ALLOWED_IN_WINDOW_MILLIS / 2) - 1);

        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
@@ -355,8 +362,9 @@ public class HighBrightnessModeControllerTest {
    // Creates instance with standard initialization values.
    private HighBrightnessModeController createDefaultHbm(OffsettableClock clock) {
        initHandler(clock);
        return new HighBrightnessModeController(mInjectorMock, mHandler, mDisplayToken, DEFAULT_MIN,
                DEFAULT_MAX, DEFAULT_HBM_DATA, () -> {}, mContextSpy);
        return new HighBrightnessModeController(mInjectorMock, mHandler, DISPLAY_WIDTH,
                DISPLAY_HEIGHT, mDisplayToken, DEFAULT_MIN, DEFAULT_MAX, DEFAULT_HBM_DATA, () -> {},
                mContextSpy, mBrightnessSetting);
    }

    private void initHandler(OffsettableClock clock) {