diff options
| author | 2024-10-07 16:28:06 +0000 | |
|---|---|---|
| committer | 2024-10-08 13:48:35 +0000 | |
| commit | b59ef4fb5eb9f21145fefc81d7da5b958df73c89 (patch) | |
| tree | c62bd6e6c2bea89206f6d78423a897da47329307 | |
| parent | f21cc714d89cc4d8780329867453903fe61ec773 (diff) | |
User refresh rate for external display
Previously smoothdisplay and force
peak refresh rate were applied to
external displays. This is incorrect
because external displays need to have
stable and predictable refresh rate.
e.g. configured to be around 59-61hz.
This cl also uses user-preferred display
mode for external displays to set
the SizeAndPhysicalRefreshRatesRange.
Test: atest DisplayModeDirectorTest DisplayObserverTest
Change-Id: I1ee3a1cc007fe913a75099397f4f482a6af1202d
Bug: 370657357
Bug: 372026069
Flag: com.android.server.display.feature.flags.enable_user_refresh_rate_for_external_display
6 files changed, 243 insertions, 28 deletions
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java index 5284d1c423f6..43860b289689 100644 --- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java +++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java @@ -207,6 +207,10 @@ public class DisplayManagerFlags { Flags.FLAG_BLOCK_AUTOBRIGHTNESS_CHANGES_ON_STYLUS_USAGE, Flags::blockAutobrightnessChangesOnStylusUsage ); + private final FlagState mIsUserRefreshRateForExternalDisplayEnabled = new FlagState( + Flags.FLAG_ENABLE_USER_REFRESH_RATE_FOR_EXTERNAL_DISPLAY, + Flags::enableUserRefreshRateForExternalDisplay + ); private final FlagState mEnableBatteryStatsForAllDisplays = new FlagState( Flags.FLAG_ENABLE_BATTERY_STATS_FOR_ALL_DISPLAYS, @@ -447,6 +451,14 @@ public class DisplayManagerFlags { } /** + * @return {@code true} if need to use user refresh rate settings for + * external displays. + */ + public boolean isUserRefreshRateForExternalDisplayEnabled() { + return mIsUserRefreshRateForExternalDisplayEnabled.isEnabled(); + } + + /** * dumps all flagstates * @param pw printWriter */ @@ -491,6 +503,7 @@ public class DisplayManagerFlags { pw.println(" " + mIdleScreenConfigInSubscribingLightSensor); pw.println(" " + mEnableBatteryStatsForAllDisplays); pw.println(" " + mBlockAutobrightnessChangesOnStylusUsage); + pw.println(" " + mIsUserRefreshRateForExternalDisplayEnabled); } private static class FlagState { diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig index 252ed09fd125..3d7c8c74be78 100644 --- a/services/core/java/com/android/server/display/feature/display_flags.aconfig +++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig @@ -373,3 +373,14 @@ flag { bug: "352411468" is_fixed_read_only: true } + +flag { + name: "enable_user_refresh_rate_for_external_display" + namespace: "display_manager" + description: "Apply refresh rate from user preferred display mode to external displays" + bug: "370657357" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java index 18e0d6ee5ea3..ffa64bfcf29f 100644 --- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java @@ -1194,6 +1194,13 @@ public class DisplayModeDirector { @GuardedBy("mLock") private void updateRefreshRateSettingLocked(float minRefreshRate, float peakRefreshRate, float defaultRefreshRate, int displayId) { + if (mDisplayObserver.isExternalDisplayLocked(displayId)) { + if (mLoggingEnabled) { + Slog.d(TAG, "skip updateRefreshRateSettingLocked for external display " + + displayId); + } + return; + } // TODO(b/156304339): The logic in here, aside from updating the refresh rate votes, is // used to predict if we're going to be doing frequent refresh rate switching, and if // so, enable the brightness observer. The logic here is more complicated and fragile @@ -1243,6 +1250,8 @@ public class DisplayModeDirector { } private void removeRefreshRateSetting(int displayId) { + mVotesStorage.updateVote(displayId, Vote.PRIORITY_USER_SETTING_PEAK_REFRESH_RATE, + null); mVotesStorage.updateVote(displayId, Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE, null); mVotesStorage.updateVote(displayId, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE, @@ -1458,11 +1467,11 @@ public class DisplayModeDirector { public void onDisplayAdded(int displayId) { updateDisplayDeviceConfig(displayId); DisplayInfo displayInfo = getDisplayInfo(displayId); + registerExternalDisplay(displayInfo); updateDisplayModes(displayId, displayInfo); updateLayoutLimitedFrameRate(displayId, displayInfo); updateUserSettingDisplayPreferredSize(displayInfo); updateDisplaysPeakRefreshRateAndResolution(displayInfo); - addDisplaysSynchronizedPeakRefreshRate(displayInfo); } @Override @@ -1477,7 +1486,7 @@ public class DisplayModeDirector { updateLayoutLimitedFrameRate(displayId, null); removeUserSettingDisplayPreferredSize(displayId); removeDisplaysPeakRefreshRateAndResolution(displayId); - removeDisplaysSynchronizedPeakRefreshRate(displayId); + unregisterExternalDisplay(displayId); } @Override @@ -1489,6 +1498,30 @@ public class DisplayModeDirector { updateUserSettingDisplayPreferredSize(displayInfo); } + private void registerExternalDisplay(DisplayInfo displayInfo) { + if (displayInfo == null || displayInfo.type != Display.TYPE_EXTERNAL) { + return; + } + synchronized (mLock) { + mExternalDisplaysConnected.add(displayInfo.displayId); + if (mExternalDisplaysConnected.size() == 1) { + addDisplaysSynchronizedPeakRefreshRate(); + } + } + } + + private void unregisterExternalDisplay(int displayId) { + synchronized (mLock) { + if (!isExternalDisplayLocked(displayId)) { + return; + } + mExternalDisplaysConnected.remove(displayId); + if (mExternalDisplaysConnected.isEmpty()) { + removeDisplaysSynchronizedPeakRefreshRate(); + } + } + } + boolean isExternalDisplayLocked(int displayId) { return mExternalDisplaysConnected.contains(displayId); } @@ -1534,10 +1567,24 @@ public class DisplayModeDirector { return; } - mVotesStorage.updateVote(info.displayId, - Vote.PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE, - Vote.forSize(/* width */ preferredMode.getPhysicalWidth(), - /* height */ preferredMode.getPhysicalHeight())); + if (info.type == Display.TYPE_EXTERNAL + && mDisplayManagerFlags.isUserRefreshRateForExternalDisplayEnabled() + && !isRefreshRateSynchronizationEnabled()) { + mVotesStorage.updateVote(info.displayId, + Vote.PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE, + Vote.forSizeAndPhysicalRefreshRatesRange( + /* minWidth */ preferredMode.getPhysicalWidth(), + /* minHeight */ preferredMode.getPhysicalHeight(), + /* width */ preferredMode.getPhysicalWidth(), + /* height */ preferredMode.getPhysicalHeight(), + /* minRefreshRate */ preferredMode.getRefreshRate(), + /* maxRefreshRate */ preferredMode.getRefreshRate())); + } else { + mVotesStorage.updateVote(info.displayId, + Vote.PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE, + Vote.forSize(/* width */ preferredMode.getPhysicalWidth(), + /* height */ preferredMode.getPhysicalHeight())); + } } @Nullable @@ -1584,17 +1631,10 @@ public class DisplayModeDirector { * Sets 60Hz target refresh rate as the vote with * {@link Vote#PRIORITY_SYNCHRONIZED_REFRESH_RATE} priority. */ - private void addDisplaysSynchronizedPeakRefreshRate(@Nullable final DisplayInfo info) { - if (info == null || info.type != Display.TYPE_EXTERNAL - || !isRefreshRateSynchronizationEnabled()) { + private void addDisplaysSynchronizedPeakRefreshRate() { + if (!isRefreshRateSynchronizationEnabled()) { return; } - synchronized (mLock) { - mExternalDisplaysConnected.add(info.displayId); - if (mExternalDisplaysConnected.size() != 1) { - return; - } - } // set minRefreshRate as the max refresh rate. mVotesStorage.updateGlobalVote(Vote.PRIORITY_SYNCHRONIZED_REFRESH_RATE, Vote.forPhysicalRefreshRates( @@ -1610,19 +1650,10 @@ public class DisplayModeDirector { + SYNCHRONIZED_REFRESH_RATE_TOLERANCE)); } - private void removeDisplaysSynchronizedPeakRefreshRate(final int displayId) { + private void removeDisplaysSynchronizedPeakRefreshRate() { if (!isRefreshRateSynchronizationEnabled()) { return; } - synchronized (mLock) { - if (!isExternalDisplayLocked(displayId)) { - return; - } - mExternalDisplaysConnected.remove(displayId); - if (!mExternalDisplaysConnected.isEmpty()) { - return; - } - } mVotesStorage.updateGlobalVote(Vote.PRIORITY_SYNCHRONIZED_REFRESH_RATE, null); mVotesStorage.updateGlobalVote(Vote.PRIORITY_SYNCHRONIZED_RENDER_FRAME_RATE, null); } diff --git a/services/core/java/com/android/server/display/mode/Vote.java b/services/core/java/com/android/server/display/mode/Vote.java index 459f9a6e8f13..f5abb0561ce7 100644 --- a/services/core/java/com/android/server/display/mode/Vote.java +++ b/services/core/java/com/android/server/display/mode/Vote.java @@ -44,7 +44,7 @@ interface Vote { // It votes [minRefreshRate, Float.POSITIVE_INFINITY] int PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE = 3; - // User setting preferred display resolution. + // User setting preferred display resolution, for external displays also includes refresh rate. int PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE = 4; // APP_REQUEST_RENDER_FRAME_RATE_RANGE is used to for internal apps to limit the render diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java index d91f154c1b87..58f0ab4411bc 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java @@ -1872,6 +1872,60 @@ public class DisplayModeDirectorTest { } @Test + public void testPeakRefreshRate_notAppliedToExternalDisplays() { + when(mDisplayManagerFlags.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled()) + .thenReturn(true); + mInjector.mDisplayInfo.type = Display.TYPE_EXTERNAL; + DisplayModeDirector director = + new DisplayModeDirector(mContext, mHandler, mInjector, + mDisplayManagerFlags, mDisplayDeviceConfigProvider); + director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON); + director.getDisplayObserver().onDisplayAdded(DISPLAY_ID); + director.getDisplayObserver().onDisplayAdded(DISPLAY_ID_2); + + Display.Mode[] modes1 = new Display.Mode[] { + new Display.Mode(/* modeId= */ 1, /* width= */ 1280, /* height= */ 720, + /* refreshRate= */ 60), + new Display.Mode(/* modeId= */ 2, /* width= */ 1280, /* height= */ 720, + /* refreshRate= */ 130), + }; + Display.Mode[] modes2 = new Display.Mode[] { + new Display.Mode(/* modeId= */ 1, /* width= */ 1280, /* height= */ 720, + /* refreshRate= */ 60), + new Display.Mode(/* modeId= */ 2, /* width= */ 1280, /* height= */ 720, + /* refreshRate= */ 140), + }; + SparseArray<Display.Mode[]> supportedModesByDisplay = new SparseArray<>(); + supportedModesByDisplay.put(DISPLAY_ID, modes1); + supportedModesByDisplay.put(DISPLAY_ID_2, modes2); + + Sensor lightSensor = createLightSensor(); + SensorManager sensorManager = createMockSensorManager(lightSensor); + director.start(sensorManager); + director.injectSupportedModesByDisplay(supportedModesByDisplay); + + // Disable Smooth Display + setPeakRefreshRate(RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE); + + Vote vote1 = director.getVote(DISPLAY_ID, + Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE); + Vote vote2 = director.getVote(DISPLAY_ID_2, + Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE); + assertThat(vote1).isNull(); + assertThat(vote2).isNull(); + + // Enable Smooth Display + setPeakRefreshRate(Float.POSITIVE_INFINITY); + + vote1 = director.getVote(DISPLAY_ID, + Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE); + vote2 = director.getVote(DISPLAY_ID_2, + Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE); + assertThat(vote1).isNull(); + assertThat(vote2).isNull(); + } + + @Test public void testPeakRefreshRate_DisplayChanged() { when(mDisplayManagerFlags.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled()) .thenReturn(true); @@ -1968,8 +2022,9 @@ public class DisplayModeDirectorTest { @Test @Parameters({ "true, true, 60", - "false, true, 50", - "true, false, 50" + "false, true, 60", + "true, false, 50", + "false, false, 50" }) public void testExternalDisplayMaxRefreshRate(boolean isRefreshRateSynchronizationEnabled, boolean isExternalDisplay, float expectedMaxRenderFrameRate) { @@ -3810,6 +3865,7 @@ public class DisplayModeDirectorTest { SensorManagerInternal sensorManagerInternal) { mDeviceConfig = new FakeDeviceConfig(); mDisplayInfo = new DisplayInfo(); + mDisplayInfo.type = Display.TYPE_INTERNAL; mDisplayInfo.defaultModeId = MODE_ID; mDisplayInfo.supportedModes = new Display.Mode[] {new Display.Mode(MODE_ID, 800, 600, /* refreshRate= */ 60)}; @@ -3856,6 +3912,7 @@ public class DisplayModeDirectorTest { @Override public boolean getDisplayInfo(int displayId, DisplayInfo displayInfo) { displayInfo.copyFrom(mDisplayInfo); + displayInfo.displayId = displayId; return mDisplayInfoValid; } diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java index 5e240cf66674..e3f150e9fbc1 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java @@ -189,6 +189,7 @@ public class DisplayObserverTest { @Test public void testExternalDisplay_voteUserPreferredMode() { when(mDisplayManagerFlags.isUserPreferredModeVoteEnabled()).thenReturn(true); + when(mDisplayManagerFlags.isUserRefreshRateForExternalDisplayEnabled()).thenReturn(false); var preferredMode = mExternalDisplayModes[5]; mExternalDisplayUserPreferredModeId = preferredMode.getModeId(); var expectedVote = Vote.forSize( @@ -229,6 +230,108 @@ public class DisplayObserverTest { .isEqualTo(null); } + /** Vote for user preferred mode */ + @Test + public void testDefaultDisplay_voteUserPreferredMode() { + when(mDisplayManagerFlags.isUserPreferredModeVoteEnabled()).thenReturn(true); + when(mDisplayManagerFlags.isUserRefreshRateForExternalDisplayEnabled()).thenReturn(true); + var preferredMode = mInternalDisplayModes[5]; + mInternalDisplayUserPreferredModeId = preferredMode.getModeId(); + var expectedVote = Vote.forSize( + preferredMode.getPhysicalWidth(), + preferredMode.getPhysicalHeight()); + init(); + assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE)) + .isEqualTo(null); + mObserver.onDisplayAdded(EXTERNAL_DISPLAY); + mObserver.onDisplayAdded(DEFAULT_DISPLAY); + assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE)) + .isEqualTo(null); + assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE)) + .isEqualTo(expectedVote); + + mInternalDisplayUserPreferredModeId = INVALID_MODE_ID; + mObserver.onDisplayChanged(EXTERNAL_DISPLAY); + mObserver.onDisplayChanged(DEFAULT_DISPLAY); + assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE)) + .isEqualTo(null); + assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE)) + .isEqualTo(null); + + preferredMode = mInternalDisplayModes[4]; + mInternalDisplayUserPreferredModeId = preferredMode.getModeId(); + expectedVote = Vote.forSize( + preferredMode.getPhysicalWidth(), + preferredMode.getPhysicalHeight()); + mObserver.onDisplayChanged(EXTERNAL_DISPLAY); + mObserver.onDisplayChanged(DEFAULT_DISPLAY); + assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE)) + .isEqualTo(expectedVote); + assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE)) + .isEqualTo(null); + + // Testing that the vote is removed. + mObserver.onDisplayRemoved(EXTERNAL_DISPLAY); + assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE)) + .isEqualTo(expectedVote); + assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE)) + .isEqualTo(null); + } + + /** Vote for user preferred mode with refresh rate, synchronization vote must be disabled */ + @Test + public void testExternalDisplay_voteUserPreferredMode_withRefreshRate() { + when(mDisplayManagerFlags.isUserPreferredModeVoteEnabled()).thenReturn(true); + when(mDisplayManagerFlags.isDisplaysRefreshRatesSynchronizationEnabled()).thenReturn(false); + when(mDisplayManagerFlags.isUserRefreshRateForExternalDisplayEnabled()).thenReturn(true); + var preferredMode = mExternalDisplayModes[5]; + mExternalDisplayUserPreferredModeId = preferredMode.getModeId(); + var expectedVote = Vote.forSizeAndPhysicalRefreshRatesRange( + preferredMode.getPhysicalWidth(), + preferredMode.getPhysicalHeight(), + preferredMode.getPhysicalWidth(), + preferredMode.getPhysicalHeight(), + preferredMode.getRefreshRate(), + preferredMode.getRefreshRate()); + init(); + assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE)) + .isEqualTo(null); + mObserver.onDisplayAdded(EXTERNAL_DISPLAY); + assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE)) + .isEqualTo(null); + assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE)) + .isEqualTo(expectedVote); + + mExternalDisplayUserPreferredModeId = INVALID_MODE_ID; + mObserver.onDisplayChanged(EXTERNAL_DISPLAY); + assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE)) + .isEqualTo(null); + assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE)) + .isEqualTo(null); + + preferredMode = mExternalDisplayModes[4]; + mExternalDisplayUserPreferredModeId = preferredMode.getModeId(); + expectedVote = Vote.forSizeAndPhysicalRefreshRatesRange( + preferredMode.getPhysicalWidth(), + preferredMode.getPhysicalHeight(), + preferredMode.getPhysicalWidth(), + preferredMode.getPhysicalHeight(), + preferredMode.getRefreshRate(), + preferredMode.getRefreshRate()); + mObserver.onDisplayChanged(EXTERNAL_DISPLAY); + assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE)) + .isEqualTo(null); + assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE)) + .isEqualTo(expectedVote); + + // Testing that the vote is removed. + mObserver.onDisplayRemoved(EXTERNAL_DISPLAY); + assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE)) + .isEqualTo(null); + assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE)) + .isEqualTo(null); + } + /** External display: Do not apply limit to user preferred mode */ @Test public void testExternalDisplay_doNotApplyLimitToUserPreferredMode() { |