diff options
7 files changed, 386 insertions, 22 deletions
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java index a39bc4e0bee5..1c0ae281d9fe 100644 --- a/core/java/android/hardware/display/DisplayManagerInternal.java +++ b/core/java/android/hardware/display/DisplayManagerInternal.java @@ -16,6 +16,7 @@ package android.hardware.display; +import android.annotation.IntDef; import android.annotation.Nullable; import android.graphics.Point; import android.hardware.SensorManager; @@ -29,6 +30,9 @@ import android.view.DisplayInfo; import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.List; import java.util.Objects; /** @@ -37,6 +41,16 @@ import java.util.Objects; * @hide Only for use within the system server. */ public abstract class DisplayManagerInternal { + + @IntDef(prefix = {"REFRESH_RATE_LIMIT_"}, value = { + REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE + }) + @Retention(RetentionPolicy.SOURCE) + public @interface RefreshRateLimitType {} + + /** Refresh rate should be limited when High Brightness Mode is active. */ + public static final int REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE = 1; + /** * Called by the power manager to initialize power management facilities. */ @@ -296,9 +310,10 @@ public abstract class DisplayManagerInternal { public abstract int getRefreshRateSwitchingType(); /** + * TODO: b/191384041 - Replace this with getRefreshRateLimitations() * Return the refresh rate restriction for the specified display and sensor pairing. If the * specified sensor is identified as an associated sensor in the specified display's - * display-device-config file, then return any refresh rate restrictions that it might specify. + * display-device-config file, then return any refresh rate restrictions that it might define. * If no restriction is specified, or the sensor is not associated with the display, then null * will be returned. * @@ -313,6 +328,15 @@ public abstract class DisplayManagerInternal { int displayId, String name, String type); /** + * Returns a list of various refresh rate limitations for the specified display. + * + * @param displayId The display to get limitations for. + * + * @return a list of {@link RefreshRateLimitation}s describing the various limits. + */ + public abstract List<RefreshRateLimitation> getRefreshRateLimitations(int displayId); + + /** * Describes the requested power state of the display. * * This object is intended to describe the general characteristics of the @@ -613,4 +637,25 @@ public abstract class DisplayManagerInternal { return "(" + min + " " + max + ")"; } } + + /** + * Describes a limitation on a display's refresh rate. Includes the allowed refresh rate + * range as well as information about when it applies, such as high-brightness-mode. + */ + public static final class RefreshRateLimitation { + @RefreshRateLimitType public int type; + + /** The range the that refresh rate should be limited to. */ + public RefreshRateRange range; + + public RefreshRateLimitation(@RefreshRateLimitType int type, float min, float max) { + this.type = type; + range = new RefreshRateRange(min, max); + } + + @Override + public String toString() { + return "RefreshRateLimitation(" + type + ": " + range + ")"; + } + } } diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index 8a8b74e40568..7ae62f7d19a4 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -19,6 +19,8 @@ package com.android.server.display; import android.annotation.NonNull; import android.content.Context; import android.content.res.Resources; +import android.hardware.display.DisplayManagerInternal; +import android.hardware.display.DisplayManagerInternal.RefreshRateLimitation; import android.os.Environment; import android.os.PowerManager; import android.text.TextUtils; @@ -86,6 +88,9 @@ public class DisplayDeviceConfig { // The details of the proximity sensor associated with this display. private final SensorData mProximitySensor = new SensorData(); + private final List<RefreshRateLimitation> mRefreshRateLimitations = + new ArrayList<>(2 /*initialCapacity*/); + // Nits and backlight values that are loaded from either the display device config file, or // config.xml. These are the raw values and just used for the dumpsys private float[] mRawNits; @@ -306,6 +311,10 @@ public class DisplayDeviceConfig { return hbmData; } + public List<RefreshRateLimitation> getRefreshRateLimitations() { + return mRefreshRateLimitations; + } + @Override public String toString() { String str = "DisplayDeviceConfig{" @@ -329,6 +338,7 @@ public class DisplayDeviceConfig { + ", mBrightnessRampSlowIncrease=" + mBrightnessRampSlowIncrease + ", mAmbientLightSensor=" + mAmbientLightSensor + ", mProximitySensor=" + mProximitySensor + + ", mRefreshRateLimitations= " + Arrays.toString(mRefreshRateLimitations.toArray()) + "}"; return str; } @@ -647,6 +657,13 @@ public class DisplayDeviceConfig { mHbmData.timeWindowMillis = hbmTiming.getTimeWindowSecs_all().longValue() * 1000; mHbmData.timeMaxMillis = hbmTiming.getTimeMaxSecs_all().longValue() * 1000; mHbmData.timeMinMillis = hbmTiming.getTimeMinSecs_all().longValue() * 1000; + final RefreshRateRange rr = hbm.getRefreshRate_all(); + if (rr != null) { + final float min = rr.getMinimum().floatValue(); + final float max = rr.getMaximum().floatValue(); + mRefreshRateLimitations.add(new RefreshRateLimitation( + DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE, min, max)); + } } } diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index bff39a932105..182a038d10f7 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -63,6 +63,7 @@ import android.hardware.display.DisplayManagerGlobal; import android.hardware.display.DisplayManagerInternal; import android.hardware.display.DisplayManagerInternal.DisplayGroupListener; import android.hardware.display.DisplayManagerInternal.DisplayTransactionListener; +import android.hardware.display.DisplayManagerInternal.RefreshRateLimitation; import android.hardware.display.DisplayManagerInternal.RefreshRateRange; import android.hardware.display.DisplayViewport; import android.hardware.display.DisplayedContentSample; @@ -130,6 +131,7 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.Optional; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicLong; @@ -2111,6 +2113,11 @@ public final class DisplayManagerService extends SystemService { DisplayManagerGlobal.EVENT_DISPLAY_BRIGHTNESS_CHANGED); } + private DisplayDevice getDeviceForDisplayLocked(int displayId) { + final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId); + return display == null ? null : display.getPrimaryDisplayDeviceLocked(); + } + private final class DisplayManagerHandler extends Handler { public DisplayManagerHandler(Looper looper) { super(looper, null, true /*async*/); @@ -3295,6 +3302,19 @@ public final class DisplayManagerService extends SystemService { } return null; } + + @Override + public List<RefreshRateLimitation> getRefreshRateLimitations(int displayId) { + final DisplayDeviceConfig config; + synchronized (mSyncRoot) { + final DisplayDevice device = getDeviceForDisplayLocked(displayId); + if (device == null) { + return null; + } + config = device.getDisplayDeviceConfig(); + } + return config.getRefreshRateLimitations(); + } } class DesiredDisplayModeSpecsObserver diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java index 2364a3c7c2ed..83fc9665f192 100644 --- a/services/core/java/com/android/server/display/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/DisplayModeDirector.java @@ -16,6 +16,8 @@ package com.android.server.display; +import static android.hardware.display.DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE; + import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ContentResolver; @@ -26,8 +28,10 @@ import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; +import android.hardware.display.BrightnessInfo; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerInternal; +import android.hardware.display.DisplayManagerInternal.RefreshRateLimitation; import android.hardware.display.DisplayManagerInternal.RefreshRateRange; import android.hardware.fingerprint.IUdfpsHbmListener; import android.net.Uri; @@ -102,6 +106,7 @@ public class DisplayModeDirector { private final DisplayObserver mDisplayObserver; private final UdfpsObserver mUdfpsObserver; private final SensorObserver mSensorObserver; + private final HbmObserver mHbmObserver; private final DeviceConfigInterface mDeviceConfig; private final DeviceConfigDisplaySettings mDeviceConfigDisplaySettings; @@ -127,7 +132,7 @@ public class DisplayModeDirector { private int mModeSwitchingType = DisplayManager.SWITCHING_TYPE_WITHIN_GROUPS; public DisplayModeDirector(@NonNull Context context, @NonNull Handler handler) { - this(context, handler, new RealInjector()); + this(context, handler, new RealInjector(context)); } public DisplayModeDirector(@NonNull Context context, @NonNull Handler handler, @@ -143,11 +148,13 @@ public class DisplayModeDirector { mDisplayObserver = new DisplayObserver(context, handler); mBrightnessObserver = new BrightnessObserver(context, handler); mUdfpsObserver = new UdfpsObserver(); - mSensorObserver = new SensorObserver(context, (displayId, priority, vote) -> { + final BallotBox ballotBox = (displayId, priority, vote) -> { synchronized (mLock) { updateVoteLocked(displayId, priority, vote); } - }); + }; + mSensorObserver = new SensorObserver(context, ballotBox); + mHbmObserver = new HbmObserver(injector, ballotBox, BackgroundThread.getHandler()); mDeviceConfigDisplaySettings = new DeviceConfigDisplaySettings(); mDeviceConfig = injector.getDeviceConfig(); mAlwaysRespectAppRequest = false; @@ -165,6 +172,7 @@ public class DisplayModeDirector { mDisplayObserver.observe(); mBrightnessObserver.observe(sensorManager); mSensorObserver.observe(); + mHbmObserver.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. @@ -596,6 +604,7 @@ public class DisplayModeDirector { mBrightnessObserver.dumpLocked(pw); mUdfpsObserver.dumpLocked(pw); mSensorObserver.dumpLocked(pw); + mHbmObserver.dumpLocked(pw); } } @@ -938,13 +947,16 @@ public class DisplayModeDirector { // user seeing the display flickering when the switches occur. public static final int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 8; + // High-brightness-mode may need a specific range of refresh-rates to function properly. + public static final int PRIORITY_HIGH_BRIGHTNESS_MODE = 9; + // 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 = 9; + public static final int PRIORITY_PROXIMITY = 10; // 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 = 10; + public static final int PRIORITY_UDFPS = 11; // 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. @@ -1021,29 +1033,30 @@ public class DisplayModeDirector { public static String priorityToString(int priority) { switch (priority) { + case PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE: + return "PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE"; + case PRIORITY_APP_REQUEST_MAX_REFRESH_RATE: + return "PRIORITY_APP_REQUEST_MAX_REFRESH_RATE"; + case PRIORITY_APP_REQUEST_SIZE: + return "PRIORITY_APP_REQUEST_SIZE"; case PRIORITY_DEFAULT_REFRESH_RATE: return "PRIORITY_DEFAULT_REFRESH_RATE"; case PRIORITY_FLICKER_REFRESH_RATE: return "PRIORITY_FLICKER_REFRESH_RATE"; case PRIORITY_FLICKER_REFRESH_RATE_SWITCH: return "PRIORITY_FLICKER_REFRESH_RATE_SWITCH"; - case PRIORITY_USER_SETTING_MIN_REFRESH_RATE: - return "PRIORITY_USER_SETTING_MIN_REFRESH_RATE"; - case PRIORITY_APP_REQUEST_MAX_REFRESH_RATE: - return "PRIORITY_APP_REQUEST_MAX_REFRESH_RATE"; - case PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE: - return "PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE"; - case PRIORITY_APP_REQUEST_SIZE: - return "PRIORITY_APP_REQUEST_SIZE"; - case PRIORITY_USER_SETTING_PEAK_REFRESH_RATE: - return "PRIORITY_USER_SETTING_PEAK_REFRESH_RATE"; + case PRIORITY_HIGH_BRIGHTNESS_MODE: + return "PRIORITY_HIGH_BRIGHTNESS_MODE"; + case PRIORITY_PROXIMITY: + return "PRIORITY_PROXIMITY"; case PRIORITY_LOW_POWER_MODE: return "PRIORITY_LOW_POWER_MODE"; case PRIORITY_UDFPS: return "PRIORITY_UDFPS"; - case PRIORITY_PROXIMITY: - return "PRIORITY_PROXIMITY"; - + case PRIORITY_USER_SETTING_MIN_REFRESH_RATE: + return "PRIORITY_USER_SETTING_MIN_REFRESH_RATE"; + case PRIORITY_USER_SETTING_PEAK_REFRESH_RATE: + return "PRIORITY_USER_SETTING_PEAK_REFRESH_RATE"; default: return Integer.toString(priority); } @@ -2155,6 +2168,75 @@ public class DisplayModeDirector { } } + /** + * Listens to DisplayManager for HBM status and applies any refresh-rate restrictions for + * 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 { + private final BallotBox mBallotBox; + private final Handler mHandler; + private final SparseBooleanArray mHbmEnabled = new SparseBooleanArray(); + private final Injector mInjector; + + private DisplayManagerInternal mDisplayManagerInternal; + + HbmObserver(Injector injector, BallotBox ballotBox, Handler handler) { + mInjector = injector; + mBallotBox = ballotBox; + mHandler = handler; + } + + public void observe() { + mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class); + mInjector.registerDisplayListener(this, mHandler, + DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS + | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED); + } + + @Override + public void onDisplayAdded(int displayId) {} + + @Override + public void onDisplayRemoved(int displayId) { + mBallotBox.vote(displayId, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE, null); + } + + @Override + public void onDisplayChanged(int displayId) { + final BrightnessInfo info = mInjector.getBrightnessInfo(displayId); + if (info == null) { + // 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)) { + // no change, ignore. + return; + } + Vote vote = null; + mHbmEnabled.put(displayId, isHbmEnabled); + if (isHbmEnabled) { + 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; + } + } + } + mBallotBox.vote(displayId, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE, vote); + } + + void dumpLocked(PrintWriter pw) { + pw.println(" HbmObserver"); + pw.println(" mHbmEnabled: " + mHbmEnabled); + } + } + private class DeviceConfigDisplaySettings implements DeviceConfig.OnPropertiesChangedListener { public DeviceConfigDisplaySettings() { } @@ -2309,10 +2391,21 @@ public class DisplayModeDirector { void registerPeakRefreshRateObserver(@NonNull ContentResolver cr, @NonNull ContentObserver observer); + + void registerDisplayListener(@NonNull DisplayManager.DisplayListener listener, + Handler handler, long flags); + + BrightnessInfo getBrightnessInfo(int displayId); } @VisibleForTesting static class RealInjector implements Injector { + private final Context mContext; + private DisplayManager mDisplayManager; + + RealInjector(Context context) { + mContext = context; + } @Override @NonNull @@ -2339,6 +2432,28 @@ public class DisplayModeDirector { cr.registerContentObserver(PEAK_REFRESH_RATE_URI, false /*notifyDescendants*/, observer, UserHandle.USER_SYSTEM); } + + @Override + public void registerDisplayListener(DisplayManager.DisplayListener listener, + Handler handler, long flags) { + getDisplayManager().registerDisplayListener(listener, handler, flags); + } + + @Override + public BrightnessInfo getBrightnessInfo(int displayId) { + final Display display = getDisplayManager().getDisplay(displayId); + if (display != null) { + return display.getBrightnessInfo(); + } + return null; + } + + private DisplayManager getDisplayManager() { + if (mDisplayManager == null) { + mDisplayManager = mContext.getSystemService(DisplayManager.class); + } + return mDisplayManager; + } } interface BallotBox { 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 3e2e37c834f1..82aaa61527d1 100644 --- a/services/core/xsd/display-device-config/display-device-config.xsd +++ b/services/core/xsd/display-device-config/display-device-config.xsd @@ -77,6 +77,10 @@ <xs:annotation name="final"/> </xs:element> <xs:element name="timing" type="hbmTiming" minOccurs="1" maxOccurs="1"/> + <xs:element type="refreshRateRange" name="refreshRate" minOccurs="0" maxOccurs="1"> + <xs:annotation name="nullable"/> + <xs:annotation name="final"/> + </xs:element> </xs:all> <xs:attribute name="enabled" type="xs:boolean" use="optional"/> </xs:complexType> diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt index d40854a87453..6e2e3625f60c 100644 --- a/services/core/xsd/display-device-config/schema/current.txt +++ b/services/core/xsd/display-device-config/schema/current.txt @@ -44,10 +44,12 @@ package com.android.server.display.config { ctor public HighBrightnessMode(); 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 public com.android.server.display.config.HbmTiming getTiming_all(); method @NonNull public final java.math.BigDecimal getTransitionPoint_all(); 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 void setTiming_all(com.android.server.display.config.HbmTiming); method public final void setTransitionPoint_all(@NonNull java.math.BigDecimal); } 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 c0df2e33ffed..cae6c863ab02 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java +++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java @@ -48,8 +48,11 @@ import android.database.ContentObserver; import android.hardware.Sensor; import android.hardware.SensorEventListener; import android.hardware.SensorManager; +import android.hardware.display.BrightnessInfo; import android.hardware.display.DisplayManager; +import android.hardware.display.DisplayManager.DisplayListener; import android.hardware.display.DisplayManagerInternal; +import android.hardware.display.DisplayManagerInternal.RefreshRateLimitation; import android.hardware.display.DisplayManagerInternal.RefreshRateRange; import android.hardware.fingerprint.IUdfpsHbmListener; import android.os.Handler; @@ -120,7 +123,7 @@ public class DisplayModeDirectorTest { mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext())); final MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContext); when(mContext.getContentResolver()).thenReturn(resolver); - mInjector = new FakesInjector(); + mInjector = spy(new FakesInjector()); mHandler = new Handler(Looper.getMainLooper()); LocalServices.removeServiceForTest(StatusBarManagerInternal.class); @@ -1256,13 +1259,163 @@ public class DisplayModeDirectorTest { assertNull(vote); } + @Test + public void testHbmVoting_forHdr() { + DisplayModeDirector director = + createDirectorFromRefreshRateArray(new float[] {60.0f, 90.0f}, 0); + 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 Limitation + when(mDisplayManagerInternalMock.getRefreshRateLimitations(DISPLAY_ID)).thenReturn( + List.of(new RefreshRateLimitation( + DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE, + 60.f, 60.f))); + + // Verify that there is no HBM vote initially + Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); + assertNull(vote); + + // Turn on HBM + when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn( + 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); + + // Turn off HBM + when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn( + new BrightnessInfo(0.45f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF)); + listener.onDisplayChanged(DISPLAY_ID); + vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); + assertNull(vote); + } + + @Test + public void testHbmVoting_forSunlight() { + DisplayModeDirector director = + createDirectorFromRefreshRateArray(new float[] {60.0f, 90.0f}, 0); + 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 Limitation + when(mDisplayManagerInternalMock.getRefreshRateLimitations(DISPLAY_ID)).thenReturn( + List.of(new RefreshRateLimitation( + DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE, + 60.f, 60.f))); + + // Verify that there is no HBM vote initially + Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); + assertNull(vote); + + // Turn on HBM + 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, 60.f); + + // 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); + } + + @Test + public void testHbmVoting_forSunlight_NoLimitation() { + DisplayModeDirector director = + createDirectorFromRefreshRateArray(new float[] {60.0f, 90.0f}, 0); + 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 Limitation for different display + when(mDisplayManagerInternalMock.getRefreshRateLimitations(DISPLAY_ID + 1)).thenReturn( + List.of(new RefreshRateLimitation( + DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE, + 60.f, 60.f))); + + // Verify that there is no HBM vote initially + Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); + assertNull(vote); + + // Turn on HBM + 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); + assertNull(vote); + + // 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); + } + + @Test + public void testHbmVoting_RemovedDisplay() { + DisplayModeDirector director = + createDirectorFromRefreshRateArray(new float[] {60.0f, 90.0f}, 0); + 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 Limitation for different display + when(mDisplayManagerInternalMock.getRefreshRateLimitations(DISPLAY_ID)).thenReturn( + List.of(new RefreshRateLimitation( + DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE, + 60.f, 60.f))); + + // Verify that there is no HBM vote initially + Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); + assertNull(vote); + + // Turn on HBM + 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, 60.f); + + // Turn off HBM + listener.onDisplayRemoved(DISPLAY_ID); + vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); + assertNull(vote); + } + private void assertVoteForRefreshRate(Vote vote, float refreshRate) { assertThat(vote).isNotNull(); final RefreshRateRange expectedRange = new RefreshRateRange(refreshRate, refreshRate); assertThat(vote.refreshRateRange).isEqualTo(expectedRange); } - private static class FakeDeviceConfig extends FakeDeviceConfigInterface { + public static class FakeDeviceConfig extends FakeDeviceConfigInterface { @Override public String getProperty(String namespace, String name) { Preconditions.checkArgument(DeviceConfig.NAMESPACE_DISPLAY_MANAGER.equals(namespace)); @@ -1403,7 +1556,7 @@ public class DisplayModeDirectorTest { mHandler.runWithScissors(() -> { }, 500 /*timeout*/); } - static class FakesInjector implements DisplayModeDirector.Injector { + public static class FakesInjector implements DisplayModeDirector.Injector { private final FakeDeviceConfig mDeviceConfig; private ContentObserver mBrightnessObserver; private ContentObserver mPeakRefreshRateObserver; @@ -1444,6 +1597,14 @@ public class DisplayModeDirectorTest { mPeakRefreshRateObserver = observer; } + @Override + public void registerDisplayListener(DisplayListener listener, Handler handler, long flag) {} + + @Override + public BrightnessInfo getBrightnessInfo(int displayId) { + return null; + } + void notifyPeakRefreshRateChanged() { if (mPeakRefreshRateObserver != null) { mPeakRefreshRateObserver.dispatchChange(false /*selfChange*/, |