diff options
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() { |