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

Commit 35676e1c authored by Oleg Blinnikov's avatar Oleg Blinnikov Committed by Cherrypicker Worker
Browse files

HBMController thermals via BrightnessThrottler

Bug: 266063390

Test: atest HighBrightnessModeControllerTest
(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:476f58d5297955154d50f87a587eee5ec98bd705)
Merged-In: Ib0c06d18457386abd6cd12211cca77804076e4cc
Change-Id: Ib0c06d18457386abd6cd12211cca77804076e4cc
parent 115b9b11
Loading
Loading
Loading
Loading
+1 −9
Original line number Diff line number Diff line
@@ -2396,7 +2396,6 @@ public class DisplayDeviceConfig {
            mHbmData.timeWindowMillis = hbmTiming.getTimeWindowSecs_all().longValue() * 1000;
            mHbmData.timeMaxMillis = hbmTiming.getTimeMaxSecs_all().longValue() * 1000;
            mHbmData.timeMinMillis = hbmTiming.getTimeMinSecs_all().longValue() * 1000;
            mHbmData.thermalStatusLimit = convertThermalStatus(hbm.getThermalStatusLimit_all());
            mHbmData.allowInLowPowerMode = hbm.getAllowInLowPowerMode_all();
            final RefreshRateRange rr = hbm.getRefreshRate_all();
            if (rr != null) {
@@ -2972,9 +2971,6 @@ public class DisplayDeviceConfig {
        /** Brightness level at which we transition from normal to high-brightness. */
        public float transitionPoint;

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

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

@@ -2993,15 +2989,13 @@ public class DisplayDeviceConfig {
        HighBrightnessModeData() {}

        HighBrightnessModeData(float minimumLux, float transitionPoint, long timeWindowMillis,
                long timeMaxMillis, long timeMinMillis,
                @PowerManager.ThermalStatus int thermalStatusLimit, boolean allowInLowPowerMode,
                long timeMaxMillis, long timeMinMillis, boolean allowInLowPowerMode,
                float minimumHdrPercentOfScreen) {
            this.minimumLux = minimumLux;
            this.transitionPoint = transitionPoint;
            this.timeWindowMillis = timeWindowMillis;
            this.timeMaxMillis = timeMaxMillis;
            this.timeMinMillis = timeMinMillis;
            this.thermalStatusLimit = thermalStatusLimit;
            this.allowInLowPowerMode = allowInLowPowerMode;
            this.minimumHdrPercentOfScreen = minimumHdrPercentOfScreen;
        }
@@ -3016,7 +3010,6 @@ public class DisplayDeviceConfig {
            other.timeMaxMillis = timeMaxMillis;
            other.timeMinMillis = timeMinMillis;
            other.transitionPoint = transitionPoint;
            other.thermalStatusLimit = thermalStatusLimit;
            other.allowInLowPowerMode = allowInLowPowerMode;
            other.minimumHdrPercentOfScreen = minimumHdrPercentOfScreen;
        }
@@ -3029,7 +3022,6 @@ public class DisplayDeviceConfig {
                    + ", timeWindow: " + timeWindowMillis + "ms"
                    + ", timeMax: " + timeMaxMillis + "ms"
                    + ", timeMin: " + timeMinMillis + "ms"
                    + ", thermalStatusLimit: " + thermalStatusLimit
                    + ", allowInLowPowerMode: " + allowInLowPowerMode
                    + ", minimumHdrPercentOfScreen: " + minimumHdrPercentOfScreen
                    + "} ";
+12 −115
Original line number Diff line number Diff line
@@ -22,13 +22,8 @@ import android.hardware.display.BrightnessInfo;
import android.net.Uri;
import android.os.Handler;
import android.os.IBinder;
import android.os.IThermalEventListener;
import android.os.IThermalService;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.Temperature;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.MathUtils;
@@ -75,7 +70,6 @@ class HighBrightnessModeController {
    private final Runnable mHbmChangeCallback;
    private final Runnable mRecalcRunnable;
    private final Clock mClock;
    private final SkinThermalStatusObserver mSkinThermalStatusObserver;
    private final Context mContext;
    private final SettingsObserver mSettingsObserver;
    private final Injector mInjector;
@@ -100,10 +94,8 @@ class HighBrightnessModeController {

    private int mHbmMode = BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
    private boolean mIsHdrLayerPresent = false;

    // mMaxDesiredHdrSdrRatio should only be applied when there is a valid backlight->nits mapping
    private float mMaxDesiredHdrSdrRatio = DEFAULT_MAX_DESIRED_HDR_SDR_RATIO;
    private boolean mIsThermalStatusWithinLimit = true;
    private boolean mIsBlockedByLowPowerMode = false;
    private int mWidth;
    private int mHeight;
@@ -138,7 +130,6 @@ class HighBrightnessModeController {
        mBrightnessMax = brightnessMax;
        mHbmChangeCallback = hbmChangeCallback;
        mHighBrightnessModeMetadata = hbmMetadata;
        mSkinThermalStatusObserver = new SkinThermalStatusObserver(mInjector, mHandler);
        mSettingsObserver = new SettingsObserver(mHandler);
        mRecalcRunnable = this::recalculateTimeAllowance;
        mHdrListener = new HdrListener();
@@ -261,7 +252,6 @@ class HighBrightnessModeController {

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

@@ -278,15 +268,10 @@ class HighBrightnessModeController {
        mDisplayStatsId = displayUniqueId.hashCode();

        unregisterHdrListener();
        mSkinThermalStatusObserver.stopObserving();
        mSettingsObserver.stopObserving();
        if (deviceSupportsHbm()) {
            registerHdrListener(displayToken);
            recalculateTimeAllowance();
            if (mHbmData.thermalStatusLimit > PowerManager.THERMAL_STATUS_NONE) {
                mIsThermalStatusWithinLimit = true;
                mSkinThermalStatusObserver.startObserving();
            }
            if (!mHbmData.allowInLowPowerMode) {
                mIsBlockedByLowPowerMode = false;
                mSettingsObserver.startObserving();
@@ -327,7 +312,6 @@ class HighBrightnessModeController {
        pw.println("  mIsTimeAvailable= " + mIsTimeAvailable);
        pw.println("  mRunningStartTimeMillis="
                + TimeUtils.formatUptime(mHighBrightnessModeMetadata.getRunningStartTimeMillis()));
        pw.println("  mIsThermalStatusWithinLimit=" + mIsThermalStatusWithinLimit);
        pw.println("  mIsBlockedByLowPowerMode=" + mIsBlockedByLowPowerMode);
        pw.println("  width*height=" + mWidth + "*" + mHeight);
        pw.println("  mEvents=");
@@ -344,8 +328,6 @@ class HighBrightnessModeController {
            }
            lastStartTime = dumpHbmEvent(pw, event);
        }

        mSkinThermalStatusObserver.dump(pw);
    }

    private long dumpHbmEvent(PrintWriter pw, HbmEvent event) {
@@ -367,7 +349,7 @@ class HighBrightnessModeController {
        // See {@link #getHdrBrightnessValue}.
        return !mIsHdrLayerPresent
                && (mIsAutoBrightnessEnabled && mIsTimeAvailable && mIsInAllowedAmbientRange
                && mIsThermalStatusWithinLimit && !mIsBlockedByLowPowerMode);
                && !mIsBlockedByLowPowerMode);
    }

    private boolean deviceSupportsHbm() {
@@ -469,7 +451,6 @@ class HighBrightnessModeController {
                    + ", isAutoBrightnessEnabled: " +  mIsAutoBrightnessEnabled
                    + ", mIsTimeAvailable: " + mIsTimeAvailable
                    + ", mIsInAllowedAmbientRange: " + mIsInAllowedAmbientRange
                    + ", mIsThermalStatusWithinLimit: " + mIsThermalStatusWithinLimit
                    + ", mIsBlockedByLowPowerMode: " + mIsBlockedByLowPowerMode
                    + ", mBrightness: " + mBrightness
                    + ", mUnthrottledBrightness: " + mUnthrottledBrightness
@@ -499,13 +480,12 @@ class HighBrightnessModeController {
    }

    private void updateHbmStats(int newMode) {
        final float transitionPoint = mHbmData.transitionPoint;
        int state = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF;
        if (newMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR
                && getHdrBrightnessValue() > transitionPoint) {
                && getHdrBrightnessValue() > mHbmData.transitionPoint) {
            state = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR;
        } else if (newMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT
                && mBrightness > transitionPoint) {
                && mBrightness > mHbmData.transitionPoint) {
            state = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT;
        }
        if (state == mHbmStatsState) {
@@ -519,16 +499,6 @@ class HighBrightnessModeController {
        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) {
@@ -541,7 +511,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 (internalThermalThrottling || externalThermalThrottling) {
            } else if (isThermalThrottlingActive()) {
                reason = FrameworkStatsLog
                                 .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_THERMAL_LIMIT;
            } else if (mIsHdrLayerPresent) {
@@ -561,6 +531,14 @@ class HighBrightnessModeController {
        mHbmStatsState = state;
    }

    @VisibleForTesting
    boolean isThermalThrottlingActive() {
        // We would've liked HBM, but we got NBM (normal brightness mode) because of thermals.
        return mUnthrottledBrightness > mHbmData.transitionPoint
                && mBrightness <= mHbmData.transitionPoint
                && mThrottlingReason == BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL;
    }

    private String hbmStatsStateToString(int hbmStatsState) {
        switch (hbmStatsState) {
        case FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF:
@@ -635,82 +613,6 @@ class HighBrightnessModeController {
        }
    }

    private final class SkinThermalStatusObserver extends IThermalEventListener.Stub {
        private final Injector mInjector;
        private final Handler mHandler;

        private IThermalService mThermalService;
        private boolean mStarted;

        SkinThermalStatusObserver(Injector injector, Handler handler) {
            mInjector = injector;
            mHandler = handler;
        }

        @Override
        public void notifyThrottling(Temperature temp) {
            if (DEBUG) {
                Slog.d(TAG, "New thermal throttling status "
                        + ", current thermal status = " + temp.getStatus()
                        + ", threshold = " + mHbmData.thermalStatusLimit);
            }
            mHandler.post(() -> {
                mIsThermalStatusWithinLimit = temp.getStatus() <= mHbmData.thermalStatusLimit;
                // This recalculates HbmMode and runs mHbmChangeCallback if the mode has changed
                updateHbmMode();
            });
        }

        void startObserving() {
            if (mStarted) {
                if (DEBUG) {
                    Slog.d(TAG, "Thermal status observer already started");
                }
                return;
            }
            mThermalService = mInjector.getThermalService();
            if (mThermalService == null) {
                Slog.w(TAG, "Could not observe thermal status. Service not available");
                return;
            }
            try {
                // We get a callback immediately upon registering so there's no need to query
                // for the current value.
                mThermalService.registerThermalEventListenerWithType(this, Temperature.TYPE_SKIN);
                mStarted = true;
            } catch (RemoteException e) {
                Slog.e(TAG, "Failed to register thermal status listener", e);
            }
        }

        void stopObserving() {
            mIsThermalStatusWithinLimit = true;
            if (!mStarted) {
                if (DEBUG) {
                    Slog.d(TAG, "Stop skipped because thermal status observer not started");
                }
                return;
            }
            try {
                mThermalService.unregisterThermalEventListener(this);
                mStarted = false;
            } catch (RemoteException e) {
                Slog.e(TAG, "Failed to unregister thermal status listener", e);
            }
            mThermalService = null;
        }

        void dump(PrintWriter writer) {
            writer.println("  SkinThermalStatusObserver:");
            writer.println("    mStarted: " + mStarted);
            if (mThermalService != null) {
                writer.println("    ThermalService available");
            } else {
                writer.println("    ThermalService not available");
            }
        }
    }

    private final class SettingsObserver extends ContentObserver {
        private final Uri mLowPowerModeSetting = Settings.Global.getUriFor(
                Settings.Global.LOW_POWER_MODE);
@@ -766,11 +668,6 @@ class HighBrightnessModeController {
            return SystemClock::uptimeMillis;
        }

        public IThermalService getThermalService() {
            return IThermalService.Stub.asInterface(
                    ServiceManager.getService(Context.THERMAL_SERVICE));
        }

        public void reportHbmStateChange(int display, int state, int reason) {
            FrameworkStatsLog.write(
                    FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED, display, state, reason);
+0 −6
Original line number Diff line number Diff line
@@ -153,12 +153,6 @@
                <xs:annotation name="nullable"/>
                <xs:annotation name="final"/>
            </xs:element>
            <!-- The highest (most severe) thermal status at which high-brightness-mode is allowed
                 to operate. -->
            <xs:element name="thermalStatusLimit" type="thermalStatus" minOccurs="0" maxOccurs="1">
                <xs:annotation name="nonnull"/>
                <xs:annotation name="final"/>
            </xs:element>
            <xs:element name="allowInLowPowerMode" type="xs:boolean" minOccurs="0" maxOccurs="1">
                <xs:annotation name="nonnull"/>
                <xs:annotation name="final"/>
+0 −2
Original line number Diff line number Diff line
@@ -156,7 +156,6 @@ package com.android.server.display.config {
    method @NonNull public final java.math.BigDecimal getMinimumLux_all();
    method @Nullable public final com.android.server.display.config.RefreshRateRange getRefreshRate_all();
    method @Nullable public final com.android.server.display.config.SdrHdrRatioMap getSdrHdrRatioMap_all();
    method @NonNull public final com.android.server.display.config.ThermalStatus getThermalStatusLimit_all();
    method public com.android.server.display.config.HbmTiming getTiming_all();
    method @NonNull public final java.math.BigDecimal getTransitionPoint_all();
    method public final void setAllowInLowPowerMode_all(@NonNull boolean);
@@ -165,7 +164,6 @@ package com.android.server.display.config {
    method public final void setMinimumLux_all(@NonNull java.math.BigDecimal);
    method public final void setRefreshRate_all(@Nullable com.android.server.display.config.RefreshRateRange);
    method public final void setSdrHdrRatioMap_all(@Nullable com.android.server.display.config.SdrHdrRatioMap);
    method public final void setThermalStatusLimit_all(@NonNull com.android.server.display.config.ThermalStatus);
    method public void setTiming_all(com.android.server.display.config.HbmTiming);
    method public final void setTransitionPoint_all(@NonNull java.math.BigDecimal);
  }
+23 −81
Original line number Diff line number Diff line
@@ -16,8 +16,6 @@

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;
@@ -29,6 +27,8 @@ import static com.android.server.display.DisplayDeviceConfig.HDR_PERCENT_OF_SCRE
import static com.android.server.display.HighBrightnessModeController.HBM_TRANSITION_POINT_INVALID;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.anyFloat;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.eq;
@@ -39,14 +39,10 @@ import static org.mockito.Mockito.when;

import android.content.Context;
import android.content.ContextWrapper;
import android.hardware.display.BrightnessInfo;
import android.os.Binder;
import android.os.Handler;
import android.os.IThermalEventListener;
import android.os.IThermalService;
import android.os.Message;
import android.os.PowerManager;
import android.os.Temperature;
import android.os.Temperature.ThrottlingStatus;
import android.os.test.TestLooper;
import android.test.mock.MockContentResolver;
import android.util.MathUtils;
@@ -66,8 +62,6 @@ import org.junit.Before;
import org.junit.Rule;
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;

@@ -80,7 +74,6 @@ public class HighBrightnessModeControllerTest {
    private static final long TIME_WINDOW_MILLIS = 55 * 1000;
    private static final long TIME_ALLOWED_IN_WINDOW_MILLIS = 12 * 1000;
    private static final long TIME_MINIMUM_AVAILABLE_TO_ENABLE_MILLIS = 5 * 1000;
    private static final int THERMAL_STATUS_LIMIT = PowerManager.THERMAL_STATUS_SEVERE;
    private static final boolean ALLOW_IN_LOW_POWER_MODE = false;

    private static final float DEFAULT_MIN = 0.01f;
@@ -102,17 +95,13 @@ public class HighBrightnessModeControllerTest {
    @Rule
    public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();

    @Mock IThermalService mThermalServiceMock;
    @Mock Injector mInjectorMock;
    @Mock HighBrightnessModeController.HdrBrightnessDeviceConfig mHdrBrightnessDeviceConfigMock;

    @Captor ArgumentCaptor<IThermalEventListener> mThermalEventListenerCaptor;

    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,
                    THERMAL_STATUS_LIMIT, ALLOW_IN_LOW_POWER_MODE,
                    HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT);
                    ALLOW_IN_LOW_POWER_MODE, HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT);

    @Before
    public void setUp() {
@@ -125,8 +114,6 @@ public class HighBrightnessModeControllerTest {
        mContextSpy = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
        final MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContextSpy);
        when(mContextSpy.getContentResolver()).thenReturn(resolver);

        when(mInjectorMock.getThermalService()).thenReturn(mThermalServiceMock);
    }

    /////////////////
@@ -321,34 +308,14 @@ public class HighBrightnessModeControllerTest {
    }

    @Test
    public void testNoHbmInHighThermalState() throws Exception {
    public void testHbmIsNotTurnedOffInHighThermalState() throws Exception {
        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());

        verify(mThermalServiceMock).registerThermalEventListenerWithType(
                mThermalEventListenerCaptor.capture(), eq(Temperature.TYPE_SKIN));
        final IThermalEventListener listener = mThermalEventListenerCaptor.getValue();

        // Set the thermal status too high.
        listener.notifyThrottling(getSkinTemp(Temperature.THROTTLING_CRITICAL));

        // Try to go into HBM mode but fail
        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
        advanceTime(10);
        // Disabled thermal throttling
        hbmc.onBrightnessChanged(/*brightness=*/ 1f, /*unthrottledBrightness*/ 1f,
                BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE);

        assertEquals(HIGH_BRIGHTNESS_MODE_OFF, hbmc.getHighBrightnessMode());
    }

    @Test
    public void testHbmTurnsOffInHighThermalState() throws Exception {
        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());

        verify(mThermalServiceMock).registerThermalEventListenerWithType(
                mThermalEventListenerCaptor.capture(), eq(Temperature.TYPE_SKIN));
        final IThermalEventListener listener = mThermalEventListenerCaptor.getValue();

        // Set the thermal status tolerable
        listener.notifyThrottling(getSkinTemp(Temperature.THROTTLING_LIGHT));
        assertFalse(hbmc.isThermalThrottlingActive());

        // Try to go into HBM mode
        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
@@ -357,15 +324,19 @@ public class HighBrightnessModeControllerTest {

        assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode());

        // Set the thermal status too high and verify we're off.
        listener.notifyThrottling(getSkinTemp(Temperature.THROTTLING_CRITICAL));
        // Enable thermal throttling
        hbmc.onBrightnessChanged(/*brightness=*/ TRANSITION_POINT - 0.01f,
                /*unthrottledBrightness*/ 1f, BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL);
        advanceTime(10);
        assertEquals(HIGH_BRIGHTNESS_MODE_OFF, hbmc.getHighBrightnessMode());
        assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode());
        assertTrue(hbmc.isThermalThrottlingActive());

        // Set the thermal status low again and verify we're back on.
        listener.notifyThrottling(getSkinTemp(Temperature.THROTTLING_SEVERE));
        // Disabled thermal throttling
        hbmc.onBrightnessChanged(/*brightness=*/ 1f, /*unthrottledBrightness*/ 1f,
                BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE);
        advanceTime(1);
        assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode());
        assertFalse(hbmc.isThermalThrottlingActive());
    }

    @Test
@@ -578,33 +549,6 @@ public class HighBrightnessModeControllerTest {
            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();

        verify(mThermalServiceMock).registerThermalEventListenerWithType(
                mThermalEventListenerCaptor.capture(), eq(Temperature.TYPE_SKIN));
        final IThermalEventListener thermListener = mThermalEventListenerCaptor.getValue();

        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
        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));

        thermListener.notifyThrottling(getSkinTemp(Temperature.THROTTLING_CRITICAL));
        advanceTime(10);
        assertEquals(HIGH_BRIGHTNESS_MODE_OFF, 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 reporting of thermal throttling when triggered externally through
    // HighBrightnessModeController.onBrightnessChanged()
    @Test
@@ -617,14 +561,16 @@ public class HighBrightnessModeControllerTest {
        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
        // Brightness is unthrottled, HBM brightness granted
        hbmc.onBrightnessChanged(hbmBrightness, hbmBrightness, BRIGHTNESS_MAX_REASON_NONE);
        hbmc.onBrightnessChanged(hbmBrightness, hbmBrightness,
                BrightnessInfo.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);
        hbmc.onBrightnessChanged(nbmBrightness, hbmBrightness,
                BrightnessInfo.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
@@ -784,11 +730,7 @@ public class HighBrightnessModeControllerTest {
        mTestLooper.dispatchAll();
    }

    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);
        hbmc.onBrightnessChanged(brightness, brightness, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE);
    }
}