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

Commit c0f2e40e authored by Oleg Blinnikov's avatar Oleg Blinnikov Committed by Android (Google) Code Review
Browse files

Merge "HBMController thermals via BrightnessThrottler"

parents f242c606 476f58d5
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);
    }
}