diff options
5 files changed, 445 insertions, 31 deletions
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index e13a7b6eac65..fc8337ac3155 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -1257,6 +1257,23 @@ public final class DisplayManager { */ String KEY_FIXED_REFRESH_RATE_HIGH_AMBIENT_BRIGHTNESS_THRESHOLDS = "fixed_refresh_rate_high_ambient_brightness_thresholds"; + + /** + * Key for refresh rate when the device is in high brightness mode for sunlight visility. + * + * @see android.provider.DeviceConfig#NAMESPACE_DISPLAY_MANAGER + * @see android.R.integer#config_defaultRefreshRateInHbmSunlight + */ + String KEY_REFRESH_RATE_IN_HBM_SUNLIGHT = "refresh_rate_in_hbm_sunlight"; + + /** + * Key for refresh rate when the device is in high brightness mode for HDR. + * + * @see android.provider.DeviceConfig#NAMESPACE_DISPLAY_MANAGER + * @see android.R.integer#config_defaultRefreshRateInHbmHdr + */ + String KEY_REFRESH_RATE_IN_HBM_HDR = "refresh_rate_in_hbm_hdr"; + /** * Key for default peak refresh rate * diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index db43b5b31e7e..acb19eb43a08 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -4492,6 +4492,13 @@ If non-positive, then the refresh rate is unchanged even if thresholds are configured. --> <integer name="config_fixedRefreshRateInHighZone">0</integer> + <!-- Default refresh rate while the device has high brightness mode enabled for Sunlight. + This value overrides values from DisplayDeviceConfig --> + <integer name="config_defaultRefreshRateInHbmSunlight">0</integer> + + <!-- Default refresh rate while the device has high brightness mode enabled for HDR. --> + <integer name="config_defaultRefreshRateInHbmHdr">0</integer> + <!-- The type of the light sensor to be used by the display framework for things like auto-brightness. If unset, then it just gets the default sensor of type TYPE_LIGHT. --> <string name="config_displayLightSensorType" translatable="false" /> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index adb046e76c88..66b792c54f80 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3948,6 +3948,8 @@ <java-symbol type="integer" name="config_defaultRefreshRateInZone" /> <java-symbol type="array" name="config_brightnessThresholdsOfPeakRefreshRate" /> <java-symbol type="array" name="config_ambientThresholdsOfPeakRefreshRate" /> + <java-symbol type="integer" name="config_defaultRefreshRateInHbmSunlight" /> + <java-symbol type="integer" name="config_defaultRefreshRateInHbmHdr" /> <!-- For fixed refresh rate displays in high brightness--> <java-symbol type="integer" name="config_fixedRefreshRateInHighZone" /> diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java index d66d7ee99f2e..5fc301e60b9d 100644 --- a/services/core/java/com/android/server/display/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/DisplayModeDirector.java @@ -36,9 +36,14 @@ import android.hardware.display.DisplayManagerInternal.RefreshRateRange; import android.hardware.fingerprint.IUdfpsHbmListener; import android.net.Uri; import android.os.Handler; +import android.os.IThermalEventListener; +import android.os.IThermalService; import android.os.Looper; import android.os.Message; +import android.os.RemoteException; +import android.os.ServiceManager; import android.os.SystemClock; +import android.os.Temperature; import android.os.UserHandle; import android.provider.DeviceConfig; import android.provider.Settings; @@ -48,6 +53,7 @@ import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; +import android.util.SparseIntArray; import android.view.Display; import android.view.DisplayInfo; @@ -72,7 +78,6 @@ import java.util.List; import java.util.Locale; import java.util.Objects; - /** * The DisplayModeDirector is responsible for determining what modes are allowed to be automatically * picked by the system based on system-wide and display-specific configuration. @@ -87,6 +92,8 @@ public class DisplayModeDirector { private static final int MSG_REFRESH_RATE_IN_LOW_ZONE_CHANGED = 4; private static final int MSG_REFRESH_RATE_IN_HIGH_ZONE_CHANGED = 5; private static final int MSG_HIGH_BRIGHTNESS_THRESHOLDS_CHANGED = 6; + private static final int MSG_REFRESH_RATE_IN_HBM_SUNLIGHT_CHANGED = 7; + private static final int MSG_REFRESH_RATE_IN_HBM_HDR_CHANGED = 8; // Special ID used to indicate that given vote is to be applied globally, rather than to a // specific display. @@ -108,6 +115,7 @@ public class DisplayModeDirector { private final UdfpsObserver mUdfpsObserver; private final SensorObserver mSensorObserver; private final HbmObserver mHbmObserver; + private final SkinThermalStatusObserver mSkinThermalStatusObserver; private final DeviceConfigInterface mDeviceConfig; private final DeviceConfigDisplaySettings mDeviceConfigDisplaySettings; @@ -155,8 +163,10 @@ public class DisplayModeDirector { } }; mSensorObserver = new SensorObserver(context, ballotBox, injector); - mHbmObserver = new HbmObserver(injector, ballotBox, BackgroundThread.getHandler()); + mSkinThermalStatusObserver = new SkinThermalStatusObserver(injector, ballotBox); mDeviceConfigDisplaySettings = new DeviceConfigDisplaySettings(); + mHbmObserver = new HbmObserver(injector, ballotBox, BackgroundThread.getHandler(), + mDeviceConfigDisplaySettings); mDeviceConfig = injector.getDeviceConfig(); mAlwaysRespectAppRequest = false; } @@ -174,6 +184,7 @@ public class DisplayModeDirector { mBrightnessObserver.observe(sensorManager); mSensorObserver.observe(); mHbmObserver.observe(); + mSkinThermalStatusObserver.observe(); synchronized (mLock) { // We may have a listener already registered before the call to start, so go ahead and // notify them to pick up our newly initialized state. @@ -606,6 +617,7 @@ public class DisplayModeDirector { mUdfpsObserver.dumpLocked(pw); mSensorObserver.dumpLocked(pw); mHbmObserver.dumpLocked(pw); + mSkinThermalStatusObserver.dumpLocked(pw); } } @@ -714,6 +726,10 @@ public class DisplayModeDirector { return mUdfpsObserver; } + @VisibleForTesting + HbmObserver getHbmObserver() { + return mHbmObserver; + } @VisibleForTesting DesiredDisplayModeSpecs getDesiredDisplayModeSpecsWithInjectedFpsSettings( @@ -784,6 +800,19 @@ public class DisplayModeDirector { (DesiredDisplayModeSpecsListener) msg.obj; desiredDisplayModeSpecsListener.onDesiredDisplayModeSpecsChanged(); break; + + case MSG_REFRESH_RATE_IN_HBM_SUNLIGHT_CHANGED: { + int refreshRateInHbmSunlight = msg.arg1; + mHbmObserver.onDeviceConfigRefreshRateInHbmSunlightChanged( + refreshRateInHbmSunlight); + break; + } + + case MSG_REFRESH_RATE_IN_HBM_HDR_CHANGED: { + int refreshRateInHbmHdr = msg.arg1; + mHbmObserver.onDeviceConfigRefreshRateInHbmHdrChanged(refreshRateInHbmHdr); + break; + } } } } @@ -910,16 +939,19 @@ public class DisplayModeDirector { // result is a range. public static final int PRIORITY_FLICKER_REFRESH_RATE = 1; + // High-brightness-mode may need a specific range of refresh-rates to function properly. + public static final int PRIORITY_HIGH_BRIGHTNESS_MODE = 2; + // SETTING_MIN_REFRESH_RATE is used to propose a lower bound of display refresh rate. // It votes [MIN_REFRESH_RATE, Float.POSITIVE_INFINITY] - public static final int PRIORITY_USER_SETTING_MIN_REFRESH_RATE = 2; + public static final int PRIORITY_USER_SETTING_MIN_REFRESH_RATE = 3; // APP_REQUEST_REFRESH_RATE_RANGE is used to for internal apps to limit the refresh // rate in certain cases, mostly to preserve power. // @see android.view.WindowManager.LayoutParams#preferredMinRefreshRate // @see android.view.WindowManager.LayoutParams#preferredMaxRefreshRate // It votes to [preferredMinRefreshRate, preferredMaxRefreshRate]. - public static final int PRIORITY_APP_REQUEST_REFRESH_RATE_RANGE = 3; + public static final int PRIORITY_APP_REQUEST_REFRESH_RATE_RANGE = 4; // We split the app request into different priorities in case we can satisfy one desire // without the other. @@ -934,32 +966,32 @@ public class DisplayModeDirector { // The preferred refresh rate is set on the main surface of the app outside of // DisplayModeDirector. // @see com.android.server.wm.WindowState#updateFrameRateSelectionPriorityIfNeeded - public static final int PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE = 4; - public static final int PRIORITY_APP_REQUEST_SIZE = 5; + public static final int PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE = 5; + public static final int PRIORITY_APP_REQUEST_SIZE = 6; // SETTING_PEAK_REFRESH_RATE has a high priority and will restrict the bounds of the rest // of low priority voters. It votes [0, max(PEAK, MIN)] - public static final int PRIORITY_USER_SETTING_PEAK_REFRESH_RATE = 6; + public static final int PRIORITY_USER_SETTING_PEAK_REFRESH_RATE = 7; // LOW_POWER_MODE force display to [0, 60HZ] if Settings.Global.LOW_POWER_MODE is on. - public static final int PRIORITY_LOW_POWER_MODE = 7; + public static final int PRIORITY_LOW_POWER_MODE = 8; // PRIORITY_FLICKER_REFRESH_RATE_SWITCH votes for disabling refresh rate switching. If the // higher priority voters' result is a range, it will fix the rate to a single choice. // It's used to avoid refresh rate switches in certain conditions which may result in the // user seeing the display flickering when the switches occur. - public static final int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 8; + public static final int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 9; - // High-brightness-mode may need a specific range of refresh-rates to function properly. - public static final int PRIORITY_HIGH_BRIGHTNESS_MODE = 9; + // Force display to [0, 60HZ] if skin temperature is at or above CRITICAL. + public static final int PRIORITY_SKIN_TEMPERATURE = 10; // The proximity sensor needs the refresh rate to be locked in order to function, so this is // set to a high priority. - public static final int PRIORITY_PROXIMITY = 10; + public static final int PRIORITY_PROXIMITY = 11; // The Under-Display Fingerprint Sensor (UDFPS) needs the refresh rate to be locked in order // to function, so this needs to be the highest priority of all votes. - public static final int PRIORITY_UDFPS = 11; + public static final int PRIORITY_UDFPS = 12; // Whenever a new priority is added, remember to update MIN_PRIORITY, MAX_PRIORITY, and // APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF, as well as priorityToString. @@ -1054,6 +1086,8 @@ public class DisplayModeDirector { return "PRIORITY_PROXIMITY"; case PRIORITY_LOW_POWER_MODE: return "PRIORITY_LOW_POWER_MODE"; + case PRIORITY_SKIN_TEMPERATURE: + return "PRIORITY_SKIN_TEMPERATURE"; case PRIORITY_UDFPS: return "PRIORITY_UDFPS"; case PRIORITY_USER_SETTING_MIN_REFRESH_RATE: @@ -2245,33 +2279,78 @@ public class DisplayModeDirector { * HBM that are associated with that display. Restrictions are retrieved from * DisplayManagerInternal but originate in the display-device-config file. */ - private static class HbmObserver implements DisplayManager.DisplayListener { + public static class HbmObserver implements DisplayManager.DisplayListener { private final BallotBox mBallotBox; private final Handler mHandler; - private final SparseBooleanArray mHbmEnabled = new SparseBooleanArray(); + private final SparseIntArray mHbmMode = new SparseIntArray(); private final Injector mInjector; + private final DeviceConfigDisplaySettings mDeviceConfigDisplaySettings; + private int mRefreshRateInHbmSunlight; + private int mRefreshRateInHbmHdr; private DisplayManagerInternal mDisplayManagerInternal; - HbmObserver(Injector injector, BallotBox ballotBox, Handler handler) { + HbmObserver(Injector injector, BallotBox ballotBox, Handler handler, + DeviceConfigDisplaySettings displaySettings) { mInjector = injector; mBallotBox = ballotBox; mHandler = handler; + mDeviceConfigDisplaySettings = displaySettings; } public void observe() { + mRefreshRateInHbmSunlight = mDeviceConfigDisplaySettings.getRefreshRateInHbmSunlight(); + mRefreshRateInHbmHdr = mDeviceConfigDisplaySettings.getRefreshRateInHbmHdr(); + mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class); mInjector.registerDisplayListener(this, mHandler, DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED); } + /** + * @return the refresh to lock to when the device is in high brightness mode for Sunlight. + */ + @VisibleForTesting + int getRefreshRateInHbmSunlight() { + return mRefreshRateInHbmSunlight; + } + + /** + * @return the refresh to lock to when the device is in high brightness mode for HDR. + */ + @VisibleForTesting + int getRefreshRateInHbmHdr() { + return mRefreshRateInHbmHdr; + } + + /** + * Recalculates the HBM vote when the device config has been changed. + */ + public void onDeviceConfigRefreshRateInHbmSunlightChanged(int refreshRate) { + if (refreshRate != mRefreshRateInHbmSunlight) { + mRefreshRateInHbmSunlight = refreshRate; + onDeviceConfigRefreshRateInHbmChanged(); + } + } + + /** + * Recalculates the HBM vote when the device config has been changed. + */ + public void onDeviceConfigRefreshRateInHbmHdrChanged(int refreshRate) { + if (refreshRate != mRefreshRateInHbmHdr) { + mRefreshRateInHbmHdr = refreshRate; + onDeviceConfigRefreshRateInHbmChanged(); + } + } + @Override public void onDisplayAdded(int displayId) {} @Override public void onDisplayRemoved(int displayId) { mBallotBox.vote(displayId, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE, null); + mHbmMode.delete(displayId); } @Override @@ -2281,31 +2360,102 @@ public class DisplayModeDirector { // Display no longer there. Assume we'll get an onDisplayRemoved very soon. return; } - final boolean isHbmEnabled = - info.highBrightnessMode != BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF; - if (isHbmEnabled == mHbmEnabled.get(displayId)) { + final int hbmMode = info.highBrightnessMode; + if (hbmMode == mHbmMode.get(displayId)) { // no change, ignore. return; } + mHbmMode.put(displayId, hbmMode); + recalculateVotesForDisplay(displayId); + } + + private void onDeviceConfigRefreshRateInHbmChanged() { + final int[] displayIds = mHbmMode.copyKeys(); + if (displayIds != null) { + for (int id : displayIds) { + recalculateVotesForDisplay(id); + } + } + } + + private void recalculateVotesForDisplay(int displayId) { + final int hbmMode = mHbmMode.get(displayId, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF); Vote vote = null; - mHbmEnabled.put(displayId, isHbmEnabled); - if (isHbmEnabled) { - final List<RefreshRateLimitation> limits = + if (hbmMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT) { + // Device resource properties take priority over DisplayDeviceConfig + if (mRefreshRateInHbmSunlight > 0) { + vote = Vote.forRefreshRates(mRefreshRateInHbmSunlight, + mRefreshRateInHbmSunlight); + } else { + final List<RefreshRateLimitation> limits = mDisplayManagerInternal.getRefreshRateLimitations(displayId); - for (int i = 0; limits != null && i < limits.size(); i++) { - final RefreshRateLimitation limitation = limits.get(i); - if (limitation.type == REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE) { - vote = Vote.forRefreshRates(limitation.range.min, limitation.range.max); - break; + for (int i = 0; limits != null && i < limits.size(); i++) { + final RefreshRateLimitation limitation = limits.get(i); + if (limitation.type == REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE) { + vote = Vote.forRefreshRates(limitation.range.min, limitation.range.max); + break; + } } } } + if (hbmMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR + && mRefreshRateInHbmHdr > 0) { + vote = Vote.forRefreshRates(mRefreshRateInHbmHdr, mRefreshRateInHbmHdr); + } mBallotBox.vote(displayId, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE, vote); } void dumpLocked(PrintWriter pw) { pw.println(" HbmObserver"); - pw.println(" mHbmEnabled: " + mHbmEnabled); + pw.println(" mHbmMode: " + mHbmMode); + pw.println(" mRefreshRateInHbmSunlight: " + mRefreshRateInHbmSunlight); + pw.println(" mRefreshRateInHbmHdr: " + mRefreshRateInHbmHdr); + } + } + + private final class SkinThermalStatusObserver extends IThermalEventListener.Stub { + private final BallotBox mBallotBox; + private final Injector mInjector; + + private @Temperature.ThrottlingStatus int mStatus = -1; + + SkinThermalStatusObserver(Injector injector, BallotBox ballotBox) { + mInjector = injector; + mBallotBox = ballotBox; + } + + @Override + public void notifyThrottling(Temperature temp) { + mStatus = temp.getStatus(); + if (mLoggingEnabled) { + Slog.d(TAG, "New thermal throttling status " + + ", current thermal status = " + mStatus); + } + final Vote vote; + if (mStatus >= Temperature.THROTTLING_CRITICAL) { + vote = Vote.forRefreshRates(0f, 60f); + } else { + vote = null; + } + mBallotBox.vote(GLOBAL_ID, Vote.PRIORITY_SKIN_TEMPERATURE, vote); + } + + public void observe() { + IThermalService thermalService = mInjector.getThermalService(); + if (thermalService == null) { + Slog.w(TAG, "Could not observe thermal status. Service not available"); + return; + } + try { + thermalService.registerThermalEventListenerWithType(this, Temperature.TYPE_SKIN); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to register thermal status listener", e); + } + } + + void dumpLocked(PrintWriter writer) { + writer.println(" SkinThermalStatusObserver:"); + writer.println(" mStatus: " + mStatus); } } @@ -2378,6 +2528,29 @@ public class DisplayModeDirector { return refreshRate; } + public int getRefreshRateInHbmSunlight() { + final int defaultRefreshRateInHbmSunlight = + mContext.getResources().getInteger( + R.integer.config_defaultRefreshRateInHbmSunlight); + + final int refreshRate = mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_SUNLIGHT, + defaultRefreshRateInHbmSunlight); + + return refreshRate; + } + + public int getRefreshRateInHbmHdr() { + final int defaultRefreshRateInHbmHdr = + mContext.getResources().getInteger(R.integer.config_defaultRefreshRateInHbmHdr); + + final int refreshRate = mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_HDR, + defaultRefreshRateInHbmHdr); + + return refreshRate; + } + /* * Return null if no such property */ @@ -2417,6 +2590,15 @@ public class DisplayModeDirector { .sendToTarget(); mHandler.obtainMessage(MSG_REFRESH_RATE_IN_HIGH_ZONE_CHANGED, refreshRateInHighZone, 0) .sendToTarget(); + + final int refreshRateInHbmSunlight = getRefreshRateInHbmSunlight(); + mHandler.obtainMessage(MSG_REFRESH_RATE_IN_HBM_SUNLIGHT_CHANGED, + refreshRateInHbmSunlight, 0) + .sendToTarget(); + + final int refreshRateInHbmHdr = getRefreshRateInHbmHdr(); + mHandler.obtainMessage(MSG_REFRESH_RATE_IN_HBM_HDR_CHANGED, refreshRateInHbmHdr, 0) + .sendToTarget(); } private int[] getIntArrayProperty(String prop) { @@ -2470,6 +2652,8 @@ public class DisplayModeDirector { BrightnessInfo getBrightnessInfo(int displayId); boolean isDozeState(Display d); + + IThermalService getThermalService(); } @VisibleForTesting @@ -2530,6 +2714,12 @@ public class DisplayModeDirector { return Display.isDozeState(d.getState()); } + @Override + public IThermalService getThermalService() { + return IThermalService.Stub.asInterface( + ServiceManager.getService(Context.THERMAL_SERVICE)); + } + private DisplayManager getDisplayManager() { if (mDisplayManager == null) { mDisplayManager = mContext.getSystemService(DisplayManager.class); diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java index 1ac28abb4c2f..0dd5c61121db 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java +++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java @@ -20,6 +20,8 @@ import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REF import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_HIGH_DISPLAY_BRIGHTNESS_THRESHOLDS; import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_LOW_AMBIENT_BRIGHTNESS_THRESHOLDS; import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_LOW_DISPLAY_BRIGHTNESS_THRESHOLDS; +import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_SUNLIGHT; +import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_HDR; import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HIGH_ZONE; import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE; @@ -56,7 +58,11 @@ import android.hardware.display.DisplayManagerInternal.RefreshRateLimitation; import android.hardware.display.DisplayManagerInternal.RefreshRateRange; import android.hardware.fingerprint.IUdfpsHbmListener; import android.os.Handler; +import android.os.IThermalEventListener; +import android.os.IThermalService; import android.os.Looper; +import android.os.RemoteException; +import android.os.Temperature; import android.provider.DeviceConfig; import android.provider.Settings; import android.test.mock.MockContentResolver; @@ -116,6 +122,8 @@ public class DisplayModeDirectorTest { public SensorManagerInternal mSensorManagerInternalMock; @Mock public DisplayManagerInternal mDisplayManagerInternalMock; + @Mock + public IThermalService mThermalServiceMock; @Before public void setUp() throws Exception { @@ -124,6 +132,7 @@ public class DisplayModeDirectorTest { final MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContext); when(mContext.getContentResolver()).thenReturn(resolver); mInjector = spy(new FakesInjector()); + when(mInjector.getThermalService()).thenReturn(mThermalServiceMock); mHandler = new Handler(Looper.getMainLooper()); LocalServices.removeServiceForTest(StatusBarManagerInternal.class); @@ -1401,6 +1410,12 @@ public class DisplayModeDirectorTest { public void testHbmVoting_forHdr() { DisplayModeDirector director = createDirectorFromRefreshRateArray(new float[] {60.0f, 90.0f}, 0); + final int hbmRefreshRate = 72; + + // Specify limitation before starting DisplayModeDirector to avoid waiting on property + // propagation + mInjector.getDeviceConfig().setRefreshRateInHbmHdr(hbmRefreshRate); + director.start(createMockSensorManager()); ArgumentCaptor<DisplayListener> captor = @@ -1425,7 +1440,7 @@ public class DisplayModeDirectorTest { new BrightnessInfo(0.45f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR)); listener.onDisplayChanged(DISPLAY_ID); vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); - assertVoteForRefreshRate(vote, 60.f); + assertVoteForRefreshRate(vote, hbmRefreshRate); // Turn off HBM when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn( @@ -1436,6 +1451,44 @@ public class DisplayModeDirectorTest { } @Test + public void testHbmObserverGetsUpdatedRefreshRateInHbmSunlight() { + DisplayModeDirector director = + createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, /* baseModeId= */ 0); + + final int initialRefreshRate = 60; + mInjector.getDeviceConfig().setRefreshRateInHbmSunlight(initialRefreshRate); + director.start(createMockSensorManager()); + assertThat(director.getHbmObserver().getRefreshRateInHbmSunlight()) + .isEqualTo(initialRefreshRate); + + final int updatedRefreshRate = 90; + mInjector.getDeviceConfig().setRefreshRateInHbmSunlight(updatedRefreshRate); + // Need to wait for the property change to propagate to the main thread. + waitForIdleSync(); + assertThat(director.getHbmObserver().getRefreshRateInHbmSunlight()) + .isEqualTo(updatedRefreshRate); + } + + @Test + public void testHbmObserverGetsUpdatedRefreshRateInHbmHdr() { + DisplayModeDirector director = + createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, /* baseModeId= */ 0); + + final int initialRefreshRate = 60; + mInjector.getDeviceConfig().setRefreshRateInHbmHdr(initialRefreshRate); + director.start(createMockSensorManager()); + assertThat(director.getHbmObserver().getRefreshRateInHbmHdr()) + .isEqualTo(initialRefreshRate); + + final int updatedRefreshRate = 90; + mInjector.getDeviceConfig().setRefreshRateInHbmHdr(updatedRefreshRate); + // Need to wait for the property change to propagate to the main thread. + waitForIdleSync(); + assertThat(director.getHbmObserver().getRefreshRateInHbmHdr()) + .isEqualTo(updatedRefreshRate); + } + + @Test public void testHbmVoting_forSunlight() { DisplayModeDirector director = createDirectorFromRefreshRateArray(new float[] {60.0f, 90.0f}, 0); @@ -1448,11 +1501,12 @@ public class DisplayModeDirectorTest { | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED)); DisplayListener listener = captor.getValue(); + final int initialRefreshRate = 60; // Specify Limitation when(mDisplayManagerInternalMock.getRefreshRateLimitations(DISPLAY_ID)).thenReturn( List.of(new RefreshRateLimitation( DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE, - 60.f, 60.f))); + initialRefreshRate, initialRefreshRate))); // Verify that there is no HBM vote initially Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); @@ -1463,7 +1517,39 @@ public class DisplayModeDirectorTest { new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT)); listener.onDisplayChanged(DISPLAY_ID); vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); - assertVoteForRefreshRate(vote, 60.f); + assertVoteForRefreshRate(vote, initialRefreshRate); + + // Change refresh rate vote value through DeviceConfig, ensure it takes precedence + final int updatedRefreshRate = 90; + mInjector.getDeviceConfig().setRefreshRateInHbmSunlight(updatedRefreshRate); + // Need to wait for the property change to propagate to the main thread. + waitForIdleSync(); + assertThat(director.getHbmObserver().getRefreshRateInHbmSunlight()) + .isEqualTo(updatedRefreshRate); + vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); + assertVoteForRefreshRate(vote, updatedRefreshRate); + + // Turn off HBM + when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn( + new BrightnessInfo(0.43f, 0.1f, 0.8f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF)); + listener.onDisplayChanged(DISPLAY_ID); + vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); + assertNull(vote); + + // Turn HBM on again and ensure the updated vote value stuck + when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn( + new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT)); + listener.onDisplayChanged(DISPLAY_ID); + vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); + assertVoteForRefreshRate(vote, updatedRefreshRate); + + // Reset DeviceConfig refresh rate, ensure vote falls back to the initial value + mInjector.getDeviceConfig().setRefreshRateInHbmSunlight(0); + // Need to wait for the property change to propagate to the main thread. + waitForIdleSync(); + assertThat(director.getHbmObserver().getRefreshRateInHbmSunlight()).isEqualTo(0); + vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); + assertVoteForRefreshRate(vote, initialRefreshRate); // Turn off HBM when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn( @@ -1511,6 +1597,63 @@ public class DisplayModeDirectorTest { assertNull(vote); } + private void setHbmAndAssertRefreshRate( + DisplayModeDirector director, DisplayListener listener, int mode, float rr) { + when(mInjector.getBrightnessInfo(DISPLAY_ID)) + .thenReturn(new BrightnessInfo(1.0f, 0.0f, 1.0f, mode)); + listener.onDisplayChanged(DISPLAY_ID); + + final Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); + if (Float.isNaN(rr)) { + assertNull(vote); + } else { + assertVoteForRefreshRate(vote, rr); + } + } + + @Test + public void testHbmVoting_forSunlightAndHdr() { + DisplayModeDirector director = + createDirectorFromRefreshRateArray(new float[] {60.0f, 90.0f}, 0); + + // Specify HDR limitation before starting DisplayModeDirector to avoid waiting on property + // propagation + final int hdrRr = 60; + mInjector.getDeviceConfig().setRefreshRateInHbmHdr(hdrRr); + director.start(createMockSensorManager()); + + ArgumentCaptor<DisplayListener> captor = ArgumentCaptor.forClass(DisplayListener.class); + verify(mInjector).registerDisplayListener(captor.capture(), any(Handler.class), + eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS + | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED)); + DisplayListener listener = captor.getValue(); + + // Specify Sunlight limitations + final float sunlightRr = 90.0f; + when(mDisplayManagerInternalMock.getRefreshRateLimitations(DISPLAY_ID)) + .thenReturn(List.of(new RefreshRateLimitation( + DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE, sunlightRr, + sunlightRr))); + + // Verify that there is no HBM vote initially + Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); + assertNull(vote); + + // Verify all state transitions + setHbmAndAssertRefreshRate( + director, listener, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT, sunlightRr); + setHbmAndAssertRefreshRate( + director, listener, BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, hdrRr); + setHbmAndAssertRefreshRate( + director, listener, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF, Float.NaN); + setHbmAndAssertRefreshRate( + director, listener, BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, hdrRr); + setHbmAndAssertRefreshRate( + director, listener, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT, sunlightRr); + setHbmAndAssertRefreshRate( + director, listener, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF, Float.NaN); + } + @Test public void testHbmVoting_RemovedDisplay() { DisplayModeDirector director = @@ -1547,12 +1690,52 @@ public class DisplayModeDirectorTest { assertNull(vote); } + @Test + public void testSkinTemperature() throws RemoteException { + DisplayModeDirector director = + createDirectorFromRefreshRateArray(new float[] {60.0f, 90.0f}, 0); + director.start(createMockSensorManager()); + + ArgumentCaptor<IThermalEventListener> thermalEventListener = + ArgumentCaptor.forClass(IThermalEventListener.class); + + verify(mThermalServiceMock).registerThermalEventListenerWithType( + thermalEventListener.capture(), eq(Temperature.TYPE_SKIN)); + final IThermalEventListener listener = thermalEventListener.getValue(); + + // Verify that there is no skin temperature vote initially. + Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_SKIN_TEMPERATURE); + assertNull(vote); + + // Set the skin temperature to critical and verify that we added a vote. + listener.notifyThrottling(getSkinTemp(Temperature.THROTTLING_CRITICAL)); + vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_SKIN_TEMPERATURE); + assertVoteForRefreshRateRange(vote, 0f, 60.f); + + // Set the skin temperature to severe and verify that the vote is gone. + listener.notifyThrottling(getSkinTemp(Temperature.THROTTLING_SEVERE)); + vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_SKIN_TEMPERATURE); + assertNull(vote); + } + + private Temperature getSkinTemp(@Temperature.ThrottlingStatus int status) { + return new Temperature(30.0f, Temperature.TYPE_SKIN, "test_skin_temp", status); + } + private void assertVoteForRefreshRate(Vote vote, float refreshRate) { assertThat(vote).isNotNull(); final RefreshRateRange expectedRange = new RefreshRateRange(refreshRate, refreshRate); assertThat(vote.refreshRateRange).isEqualTo(expectedRange); } + private void assertVoteForRefreshRateRange( + Vote vote, float refreshRateLow, float refreshRateHigh) { + assertThat(vote).isNotNull(); + final RefreshRateRange expectedRange = + new RefreshRateRange(refreshRateLow, refreshRateHigh); + assertThat(vote.refreshRateRange).isEqualTo(expectedRange); + } + public static class FakeDeviceConfig extends FakeDeviceConfigInterface { @Override public String getProperty(String namespace, String name) { @@ -1575,6 +1758,16 @@ public class DisplayModeDirectorTest { String.valueOf(fps)); } + void setRefreshRateInHbmSunlight(int fps) { + putPropertyAndNotify(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + KEY_REFRESH_RATE_IN_HBM_SUNLIGHT, String.valueOf(fps)); + } + + void setRefreshRateInHbmHdr(int fps) { + putPropertyAndNotify(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + KEY_REFRESH_RATE_IN_HBM_HDR, String.valueOf(fps)); + } + void setLowDisplayBrightnessThresholds(int[] brightnessThresholds) { String thresholds = toPropertyValue(brightnessThresholds); @@ -1748,6 +1941,11 @@ public class DisplayModeDirectorTest { return false; } + @Override + public IThermalService getThermalService() { + return null; + } + void notifyPeakRefreshRateChanged() { if (mPeakRefreshRateObserver != null) { mPeakRefreshRateObserver.dispatchChange(false /*selfChange*/, |