diff options
7 files changed, 408 insertions, 28 deletions
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java index 1a07cb854cae..a4a5f96c7358 100644 --- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java +++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java @@ -68,6 +68,7 @@ class AutomaticBrightnessController { private static final int MSG_INVALIDATE_SHORT_TERM_MODEL = 3; private static final int MSG_UPDATE_FOREGROUND_APP = 4; private static final int MSG_UPDATE_FOREGROUND_APP_SYNC = 5; + private static final int MSG_RUN_UPDATE = 6; // Length of the ambient light horizon used to calculate the long term estimate of ambient // light. @@ -360,6 +361,13 @@ class AutomaticBrightnessController { return mBrightnessMapper.getDefaultConfig(); } + /** + * Force recalculate of the state of automatic brightness. + */ + public void update() { + mHandler.sendEmptyMessage(MSG_RUN_UPDATE); + } + private boolean setDisplayPolicy(int policy) { if (mDisplayPolicy == policy) { return false; @@ -910,6 +918,10 @@ class AutomaticBrightnessController { @Override public void handleMessage(Message msg) { switch (msg.what) { + case MSG_RUN_UPDATE: + updateAutoBrightness(true /*sendUpdate*/, false /*isManuallySet*/); + break; + case MSG_UPDATE_AMBIENT_LUX: updateAmbientLux(); break; diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index 88f88a8439cc..4c9d0f2691b3 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -39,6 +39,7 @@ import com.android.server.display.config.NitsMap; 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.XmlParser; import org.xmlpull.v1.XmlPullParserException; @@ -657,6 +658,8 @@ 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) { final float min = rr.getMinimum().floatValue(); @@ -743,6 +746,31 @@ public class DisplayDeviceConfig { } } + private @PowerManager.ThermalStatus int convertThermalStatus(ThermalStatus value) { + if (value == null) { + return PowerManager.THERMAL_STATUS_NONE; + } + switch (value) { + case none: + return PowerManager.THERMAL_STATUS_NONE; + case light: + return PowerManager.THERMAL_STATUS_LIGHT; + case moderate: + return PowerManager.THERMAL_STATUS_MODERATE; + case severe: + return PowerManager.THERMAL_STATUS_SEVERE; + case critical: + return PowerManager.THERMAL_STATUS_CRITICAL; + case emergency: + return PowerManager.THERMAL_STATUS_EMERGENCY; + case shutdown: + return PowerManager.THERMAL_STATUS_SHUTDOWN; + default: + Slog.wtf(TAG, "Unexpected Thermal Status: " + value); + return PowerManager.THERMAL_STATUS_NONE; + } + } + static class SensorData { public String type; public String name; @@ -781,6 +809,12 @@ 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; + /** Time window for HBM. */ public long timeWindowMillis; @@ -792,13 +826,16 @@ public class DisplayDeviceConfig { HighBrightnessModeData() {} - HighBrightnessModeData(float minimumLux, float transitionPoint, - long timeWindowMillis, long timeMaxMillis, long timeMinMillis) { + HighBrightnessModeData(float minimumLux, float transitionPoint, long timeWindowMillis, + long timeMaxMillis, long timeMinMillis, + @PowerManager.ThermalStatus int thermalStatusLimit, boolean allowInLowPowerMode) { this.minimumLux = minimumLux; this.transitionPoint = transitionPoint; this.timeWindowMillis = timeWindowMillis; this.timeMaxMillis = timeMaxMillis; this.timeMinMillis = timeMinMillis; + this.thermalStatusLimit = thermalStatusLimit; + this.allowInLowPowerMode = allowInLowPowerMode; } /** @@ -807,10 +844,12 @@ public class DisplayDeviceConfig { */ public void copyTo(@NonNull HighBrightnessModeData other) { other.minimumLux = minimumLux; - other.transitionPoint = transitionPoint; other.timeWindowMillis = timeWindowMillis; other.timeMaxMillis = timeMaxMillis; other.timeMinMillis = timeMinMillis; + other.transitionPoint = transitionPoint; + other.thermalStatusLimit = thermalStatusLimit; + other.allowInLowPowerMode = allowInLowPowerMode; } @Override @@ -821,6 +860,8 @@ public class DisplayDeviceConfig { + ", timeWindow: " + timeWindowMillis + "ms" + ", timeMax: " + timeMaxMillis + "ms" + ", timeMin: " + timeMinMillis + "ms" + + ", thermalStatusLimit: " + thermalStatusLimit + + ", allowInLowPowerMode: " + allowInLowPowerMode + "} "; } } diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 555add4b027f..8396f664146d 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -1518,7 +1518,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call () -> { sendUpdatePowerStateLocked(); mHandler.post(mOnBrightnessChangeRunnable); - }); + // TODO(b/192258832): Switch the HBMChangeCallback to a listener pattern. + mAutomaticBrightnessController.update(); + }, mContext); } private void blockScreenOn() { diff --git a/services/core/java/com/android/server/display/HighBrightnessModeController.java b/services/core/java/com/android/server/display/HighBrightnessModeController.java index 57a8c4b998ae..d6294223556d 100644 --- a/services/core/java/com/android/server/display/HighBrightnessModeController.java +++ b/services/core/java/com/android/server/display/HighBrightnessModeController.java @@ -16,11 +16,21 @@ package com.android.server.display; +import android.content.Context; +import android.database.ContentObserver; 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.Slog; import android.util.TimeUtils; import android.view.SurfaceControlHdrLayerInfoListener; @@ -52,6 +62,10 @@ 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; private SurfaceControlHdrLayerInfoListener mHdrListener; private HighBrightnessModeData mHbmData; @@ -63,6 +77,8 @@ class HighBrightnessModeController { private float mAutoBrightness; private int mHbmMode = BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF; private boolean mIsHdrLayerPresent = false; + private boolean mIsThermalStatusWithinLimit = true; + private boolean mIsBlockedByLowPowerMode = false; /** * If HBM is currently running, this is the start time for the current HBM session. @@ -72,29 +88,33 @@ class HighBrightnessModeController { /** * List of previous HBM-events ordered from most recent to least recent. * Meant to store only the events that fall into the most recent - * {@link mHbmData.timeWindowSecs}. + * {@link mHbmData.timeWindowMillis}. */ private LinkedList<HbmEvent> mEvents = new LinkedList<>(); HighBrightnessModeController(Handler handler, IBinder displayToken, float brightnessMin, - float brightnessMax, HighBrightnessModeData hbmData, Runnable hbmChangeCallback) { - this(SystemClock::uptimeMillis, handler, displayToken, brightnessMin, brightnessMax, - hbmData, hbmChangeCallback); + float brightnessMax, HighBrightnessModeData hbmData, Runnable hbmChangeCallback, + Context context) { + this(new Injector(), handler, displayToken, brightnessMin, brightnessMax, + hbmData, hbmChangeCallback, context); } @VisibleForTesting - HighBrightnessModeController(Clock clock, Handler handler, IBinder displayToken, + HighBrightnessModeController(Injector injector, Handler handler, IBinder displayToken, float brightnessMin, float brightnessMax, HighBrightnessModeData hbmData, - Runnable hbmChangeCallback) { - mClock = clock; + Runnable hbmChangeCallback, Context context) { + mInjector = injector; + mClock = injector.getClock(); mHandler = handler; mBrightnessMin = brightnessMin; mBrightnessMax = brightnessMax; 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); } @@ -178,14 +198,26 @@ class HighBrightnessModeController { void stop() { registerHdrListener(null /*displayToken*/); + mSkinThermalStatusObserver.stopObserving(); + mSettingsObserver.stopObserving(); } void resetHbmData(IBinder displayToken, HighBrightnessModeData hbmData) { mHbmData = hbmData; 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(); + } } } @@ -208,6 +240,8 @@ class HighBrightnessModeController { pw.println(" mBrightnessMin=" + mBrightnessMin); pw.println(" mBrightnessMax=" + mBrightnessMax); pw.println(" mRunningStartTimeMillis=" + TimeUtils.formatUptime(mRunningStartTimeMillis)); + pw.println(" mIsThermalStatusWithinLimit=" + mIsThermalStatusWithinLimit); + pw.println(" mIsBlockedByLowPowerMode=" + mIsBlockedByLowPowerMode); pw.println(" mEvents="); final long currentTime = mClock.uptimeMillis(); long lastStartTime = currentTime; @@ -221,6 +255,8 @@ class HighBrightnessModeController { } lastStartTime = dumpHbmEvent(pw, event); } + + mSkinThermalStatusObserver.dump(pw); } private long dumpHbmEvent(PrintWriter pw, HbmEvent event) { @@ -234,7 +270,8 @@ class HighBrightnessModeController { private boolean isCurrentlyAllowed() { return mIsHdrLayerPresent - || (mIsAutoBrightnessEnabled && mIsTimeAvailable && mIsInAllowedAmbientRange); + || (mIsAutoBrightnessEnabled && mIsTimeAvailable && mIsInAllowedAmbientRange + && mIsThermalStatusWithinLimit && !mIsBlockedByLowPowerMode); } private boolean deviceSupportsHbm() { @@ -327,6 +364,12 @@ class HighBrightnessModeController { + ", remainingAllowedTime: " + remainingTime + ", isLuxHigh: " + mIsInAllowedAmbientRange + ", isHBMCurrentlyAllowed: " + isCurrentlyAllowed() + + ", isHdrLayerPresent: " + mIsHdrLayerPresent + + ", isAutoBrightnessEnabled: " + mIsAutoBrightnessEnabled + + ", mIsTimeAvailable: " + mIsTimeAvailable + + ", mIsInAllowedAmbientRange: " + mIsInAllowedAmbientRange + + ", mIsThermalStatusWithinLimit: " + mIsThermalStatusWithinLimit + + ", mIsBlockedByLowPowerMode: " + mIsBlockedByLowPowerMode + ", brightness: " + mAutoBrightness + ", RunningStartTimeMillis: " + mRunningStartTimeMillis + ", nextTimeout: " + (nextTimeout != -1 ? (nextTimeout - currentTime) : -1) @@ -337,8 +380,11 @@ class HighBrightnessModeController { mHandler.removeCallbacks(mRecalcRunnable); mHandler.postAtTime(mRecalcRunnable, nextTimeout + 1); } - // Update the state of the world + updateHbmMode(); + } + + private void updateHbmMode() { int newHbmMode = calculateHighBrightnessMode(); if (mHbmMode != newHbmMode) { mHbmMode = newHbmMode; @@ -409,4 +455,141 @@ 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); + private boolean mStarted; + + SettingsObserver(Handler handler) { + super(handler); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + updateLowPower(); + } + + void startObserving() { + if (!mStarted) { + mContext.getContentResolver().registerContentObserver(mLowPowerModeSetting, + false /*notifyForDescendants*/, this, UserHandle.USER_ALL); + mStarted = true; + updateLowPower(); + } + } + + void stopObserving() { + mIsBlockedByLowPowerMode = false; + if (mStarted) { + mContext.getContentResolver().unregisterContentObserver(this); + mStarted = false; + } + } + + private void updateLowPower() { + final boolean isLowPowerMode = isLowPowerMode(); + if (isLowPowerMode == mIsBlockedByLowPowerMode) { + return; + } + if (DEBUG) { + Slog.d(TAG, "Settings.Global.LOW_POWER_MODE enabled: " + isLowPowerMode); + } + mIsBlockedByLowPowerMode = isLowPowerMode; + // this recalculates HbmMode and runs mHbmChangeCallback if the mode has changed + updateHbmMode(); + } + + private boolean isLowPowerMode() { + return Settings.Global.getInt( + mContext.getContentResolver(), Settings.Global.LOW_POWER_MODE, 0) != 0; + } + } + + public static class Injector { + public Clock getClock() { + return SystemClock::uptimeMillis; + } + + public IThermalService getThermalService() { + return IThermalService.Stub.asInterface( + ServiceManager.getService(Context.THERMAL_SERVICE)); + } + } } 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 82aaa61527d1..429edf175be4 100644 --- a/services/core/xsd/display-device-config/display-device-config.xsd +++ b/services/core/xsd/display-device-config/display-device-config.xsd @@ -81,6 +81,16 @@ <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"/> + </xs:element> </xs:all> <xs:attribute name="enabled" type="xs:boolean" use="optional"/> </xs:complexType> @@ -102,6 +112,19 @@ </xs:all> </xs:complexType> + <!-- Maps to PowerManager.THERMAL_STATUS_* values. --> + <xs:simpleType name="thermalStatus"> + <xs:restriction base="xs:string"> + <xs:enumeration value="none"/> + <xs:enumeration value="light"/> + <xs:enumeration value="moderate"/> + <xs:enumeration value="severe"/> + <xs:enumeration value="critical"/> + <xs:enumeration value="emergency"/> + <xs:enumeration value="shutdown"/> + </xs:restriction> + </xs:simpleType> + <xs:complexType name="nitsMap"> <xs:sequence> <xs:element name="point" type="point" maxOccurs="unbounded" minOccurs="2"> diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt index 6e2e3625f60c..ad186026d30c 100644 --- a/services/core/xsd/display-device-config/schema/current.txt +++ b/services/core/xsd/display-device-config/schema/current.txt @@ -42,14 +42,18 @@ package com.android.server.display.config { public class HighBrightnessMode { ctor public HighBrightnessMode(); + method @NonNull public final boolean getAllowInLowPowerMode_all(); method public boolean getEnabled(); method @NonNull public final java.math.BigDecimal getMinimumLux_all(); method @Nullable public final com.android.server.display.config.RefreshRateRange getRefreshRate_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); method public void setEnabled(boolean); 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 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); } @@ -85,6 +89,17 @@ package com.android.server.display.config { method public final void setType(@Nullable String); } + public enum ThermalStatus { + method public String getRawName(); + enum_constant public static final com.android.server.display.config.ThermalStatus critical; + enum_constant public static final com.android.server.display.config.ThermalStatus emergency; + enum_constant public static final com.android.server.display.config.ThermalStatus light; + enum_constant public static final com.android.server.display.config.ThermalStatus moderate; + enum_constant public static final com.android.server.display.config.ThermalStatus none; + enum_constant public static final com.android.server.display.config.ThermalStatus severe; + enum_constant public static final com.android.server.display.config.ThermalStatus shutdown; + } + public class XmlParser { ctor public XmlParser(); method public static com.android.server.display.config.DisplayConfiguration read(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException; 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 8e4cdc91d0e6..fbcf53d3bd4a 100644 --- a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java @@ -20,22 +20,43 @@ import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF; import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT; import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import android.content.Context; +import android.content.ContextWrapper; 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.platform.test.annotations.Presubmit; +import android.test.mock.MockContentResolver; +import androidx.test.core.app.ApplicationProvider; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.util.test.FakeSettingsProvider; +import com.android.internal.util.test.FakeSettingsProviderRule; import com.android.server.display.DisplayDeviceConfig.HighBrightnessModeData; +import com.android.server.display.HighBrightnessModeController.Injector; import com.android.server.testutils.OffsettableClock; 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; @SmallTest @Presubmit @@ -47,6 +68,8 @@ 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; private static final float DEFAULT_MAX = 0.80f; @@ -57,22 +80,30 @@ public class HighBrightnessModeControllerTest { private TestLooper mTestLooper; private Handler mHandler; private Binder mDisplayToken; + private Context mContextSpy; + + @Rule + public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule(); + + @Mock IThermalService mThermalServiceMock; + @Mock Injector mInjectorMock; + + @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); + TIME_ALLOWED_IN_WINDOW_MILLIS, TIME_MINIMUM_AVAILABLE_TO_ENABLE_MILLIS, + THERMAL_STATUS_LIMIT, ALLOW_IN_LOW_POWER_MODE); @Before public void setUp() { - mClock = new OffsettableClock.Stopped(); - mTestLooper = new TestLooper(mClock::now); + MockitoAnnotations.initMocks(this); mDisplayToken = null; - mHandler = new Handler(mTestLooper.getLooper(), new Handler.Callback() { - @Override - public boolean handleMessage(Message msg) { - return true; - } - }); + mContextSpy = spy(new ContextWrapper(ApplicationProvider.getApplicationContext())); + final MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContextSpy); + when(mContextSpy.getContentResolver()).thenReturn(resolver); + + when(mInjectorMock.getThermalService()).thenReturn(mThermalServiceMock); } ///////////////// @@ -81,15 +112,19 @@ public class HighBrightnessModeControllerTest { @Test public void testNoHbmData() { + initHandler(null); final HighBrightnessModeController hbmc = new HighBrightnessModeController( - mClock::now, mHandler, mDisplayToken, DEFAULT_MIN, DEFAULT_MAX, null, () -> {}); + mInjectorMock, mHandler, mDisplayToken, DEFAULT_MIN, DEFAULT_MAX, null, + () -> {}, mContextSpy); assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_OFF); } @Test public void testNoHbmData_Enabled() { + initHandler(null); final HighBrightnessModeController hbmc = new HighBrightnessModeController( - mClock::now, mHandler, mDisplayToken, DEFAULT_MIN, DEFAULT_MAX, null, () -> {}); + mInjectorMock, mHandler, mDisplayToken, DEFAULT_MIN, DEFAULT_MAX, null, + () -> {}, mContextSpy); hbmc.setAutoBrightnessEnabled(true); hbmc.onAmbientLuxChange(MINIMUM_LUX - 1); // below allowed range assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_OFF); @@ -258,6 +293,54 @@ public class HighBrightnessModeControllerTest { assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF); } + @Test + public void testNoHbmInHighThermalState() 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(true); + hbmc.onAmbientLuxChange(MINIMUM_LUX + 1); + advanceTime(10); + + 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)); + + // Try to go into HBM mode + hbmc.setAutoBrightnessEnabled(true); + hbmc.onAmbientLuxChange(MINIMUM_LUX + 1); + advanceTime(1); + + assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode()); + + // Set the thermal status too high and verify we're off. + listener.notifyThrottling(getSkinTemp(Temperature.THROTTLING_CRITICAL)); + advanceTime(10); + assertEquals(HIGH_BRIGHTNESS_MODE_OFF, hbmc.getHighBrightnessMode()); + + // Set the thermal status low again and verify we're back on. + listener.notifyThrottling(getSkinTemp(Temperature.THROTTLING_SEVERE)); + advanceTime(1); + assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode()); + } + private void assertState(HighBrightnessModeController hbmc, float brightnessMin, float brightnessMax, int hbmMode) { assertEquals(brightnessMin, hbmc.getCurrentBrightnessMin(), EPSILON); @@ -265,14 +348,35 @@ public class HighBrightnessModeControllerTest { assertEquals(hbmMode, hbmc.getHighBrightnessMode()); } - // Creates instance with standard initialization values. private HighBrightnessModeController createDefaultHbm() { - return new HighBrightnessModeController(mClock::now, mHandler, mDisplayToken, DEFAULT_MIN, - DEFAULT_MAX, DEFAULT_HBM_DATA, () -> {}); + return createDefaultHbm(null); + } + + // 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); + } + + private void initHandler(OffsettableClock clock) { + mClock = clock != null ? clock : new OffsettableClock.Stopped(); + when(mInjectorMock.getClock()).thenReturn(mClock::now); + mTestLooper = new TestLooper(mClock::now); + mHandler = new Handler(mTestLooper.getLooper(), new Handler.Callback() { + @Override + public boolean handleMessage(Message msg) { + return true; + } + }); } private void advanceTime(long timeMs) { mClock.fastForward(timeMs); mTestLooper.dispatchAll(); } + + private Temperature getSkinTemp(@ThrottlingStatus int status) { + return new Temperature(30.0f, Temperature.TYPE_SKIN, "test_skin_temp", status); + } } |