From 26c834522be4f02919c69eeb12a3ed1092f07ca4 Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Wed, 14 Apr 2021 13:32:23 -0700 Subject: [PATCH 1/2] Add preferredMaxDisplayRefreshRate Add a new private window attribute for allowing apps to specify the max display refresh rate. This is useful for use cases such as keyguard where the refresh rate should be limited to preserve power. In the next CLs the preferredDiplayModeId would be enabled for frame rate override (60-on-120) and the preferredMaxDisplayRefreshRate would be the alternative to control the display refresh rate (as opposed to the frame rate experienced by the app). Test: atest RefreshRatePolicyTest Test: atest DisplayModeDirectorTest Test: Launch camera app and observe refresh rate Bug: 183226498 Change-Id: I1c5e9f6047cbea4bfb581251b8dd2b9058b3e378 --- .../display/DisplayManagerInternal.java | 6 +- core/java/android/view/WindowManager.java | 8 + ...NotificationShadeWindowControllerImpl.java | 22 +-- .../server/display/DisplayManagerService.java | 13 +- .../server/display/DisplayModeDirector.java | 53 +++++- .../com/android/server/wm/DisplayContent.java | 23 ++- .../android/server/wm/RefreshRatePolicy.java | 33 +++- .../display/DisplayModeDirectorTest.java | 160 ++++++++++++++++++ .../server/wm/RefreshRatePolicyTest.java | 26 ++- 9 files changed, 299 insertions(+), 45 deletions(-) diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java index 2d58520a942e7..dce3fefff285e 100644 --- a/core/java/android/hardware/display/DisplayManagerInternal.java +++ b/core/java/android/hardware/display/DisplayManagerInternal.java @@ -190,6 +190,8 @@ public abstract class DisplayManagerInternal { * has a preference. * @param requestedModeId The preferred mode id for the top-most visible window that has a * preference. + * @param requestedMaxRefreshRate The preferred highest refresh rate for the top-most visible + * window that has a preference. * @param requestedMinimalPostProcessing The preferred minimal post processing setting for the * display. This is true when there is at least one visible window that wants minimal post * processng on. @@ -197,8 +199,8 @@ public abstract class DisplayManagerInternal { * prior to call to performTraversalInTransactionFromWindowManager. */ public abstract void setDisplayProperties(int displayId, boolean hasContent, - float requestedRefreshRate, int requestedModeId, boolean requestedMinimalPostProcessing, - boolean inTraversal); + float requestedRefreshRate, int requestedModeId, float requestedMaxRefreshRate, + boolean requestedMinimalPostProcessing, boolean inTraversal); /** * Applies an offset to the contents of a display, for example to avoid burn-in. diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index c32ab3a2d717e..c1e394d7456a2 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -3009,6 +3009,14 @@ public interface WindowManager extends ViewManager { */ public int preferredDisplayModeId; + /** + * The max display refresh rate while the window is in focus. + * + * This value is ignored if {@link #preferredDisplayModeId} is set. + * @hide + */ + public float preferredMaxDisplayRefreshRate; + /** * An internal annotation for flags that can be specified to {@link #systemUiVisibility} * and {@link #subtreeSystemUiVisibility}. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java index 388d72da808d9..ae018ba4fe766 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java @@ -61,7 +61,6 @@ import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.lang.reflect.Field; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashSet; import java.util.Set; import java.util.function.Consumer; @@ -85,7 +84,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW private final LayoutParams mLpChanged; private final boolean mKeyguardScreenRotation; private final long mLockScreenDisplayTimeout; - private final Display.Mode mKeyguardDisplayMode; + private final float mKeyguardRefreshRate; private final KeyguardViewMediator mKeyguardViewMediator; private final KeyguardBypassController mKeyguardBypassController; private ViewGroup mNotificationShadeView; @@ -135,14 +134,8 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW // Running on the highest frame rate available can be expensive. // Let's specify a preferred refresh rate, and allow higher FPS only when we // know that we're not falsing (because we unlocked.) - int keyguardRefreshRate = context.getResources() + mKeyguardRefreshRate = context.getResources() .getInteger(R.integer.config_keyguardRefreshRate); - // Find supported display mode with the same resolution and requested refresh rate. - mKeyguardDisplayMode = Arrays.stream(supportedModes).filter(mode -> - (int) mode.getRefreshRate() == keyguardRefreshRate - && mode.getPhysicalWidth() == currentMode.getPhysicalWidth() - && mode.getPhysicalHeight() == currentMode.getPhysicalHeight()) - .findFirst().orElse(null); } /** @@ -273,16 +266,17 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW mLpChanged.privateFlags &= ~LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; } - if (mKeyguardDisplayMode != null) { + if (mKeyguardRefreshRate > 0) { boolean bypassOnKeyguard = mKeyguardBypassController.getBypassEnabled() && state.mStatusBarState == StatusBarState.KEYGUARD && !state.mKeyguardFadingAway && !state.mKeyguardGoingAway; if (state.mDozing || bypassOnKeyguard) { - mLpChanged.preferredDisplayModeId = mKeyguardDisplayMode.getModeId(); + mLpChanged.preferredMaxDisplayRefreshRate = mKeyguardRefreshRate; } else { - mLpChanged.preferredDisplayModeId = 0; + mLpChanged.preferredMaxDisplayRefreshRate = 0; } - Trace.setCounter("display_mode_id", mLpChanged.preferredDisplayModeId); + Trace.setCounter("display_max_refresh_rate", + (long) mLpChanged.preferredMaxDisplayRefreshRate); } } @@ -669,7 +663,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println(TAG + ":"); - pw.println(" mKeyguardDisplayMode=" + mKeyguardDisplayMode); + pw.println(" mKeyguardRefreshRate=" + mKeyguardRefreshRate); pw.println(mCurrentState); if (mNotificationShadeView != null && mNotificationShadeView.getViewRootImpl() != null) { mNotificationShadeView.getViewRootImpl().dump(" ", pw); diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index f75f3e1f2f79e..d4920f56a27e2 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -1498,8 +1498,8 @@ public final class DisplayManagerService extends SystemService { } private void setDisplayPropertiesInternal(int displayId, boolean hasContent, - float requestedRefreshRate, int requestedModeId, boolean preferMinimalPostProcessing, - boolean inTraversal) { + float requestedRefreshRate, int requestedModeId, float requestedMaxRefreshRate, + boolean preferMinimalPostProcessing, boolean inTraversal) { synchronized (mSyncRoot) { final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId); if (display == null) { @@ -1523,8 +1523,8 @@ public final class DisplayManagerService extends SystemService { requestedModeId = display.getDisplayInfoLocked().findDefaultModeByRefreshRate( requestedRefreshRate).getModeId(); } - mDisplayModeDirector.getAppRequestObserver().setAppRequestedMode( - displayId, requestedModeId); + mDisplayModeDirector.getAppRequestObserver().setAppRequest( + displayId, requestedModeId, requestedMaxRefreshRate); if (display.getDisplayInfoLocked().minimalPostProcessingSupported) { boolean mppRequest = mMinimalPostProcessingAllowed && preferMinimalPostProcessing; @@ -3189,10 +3189,11 @@ public final class DisplayManagerService extends SystemService { @Override public void setDisplayProperties(int displayId, boolean hasContent, - float requestedRefreshRate, int requestedMode, + float requestedRefreshRate, int requestedMode, float requestedMaxRefreshRate, boolean requestedMinimalPostProcessing, boolean inTraversal) { setDisplayPropertiesInternal(displayId, hasContent, requestedRefreshRate, - requestedMode, requestedMinimalPostProcessing, inTraversal); + requestedMode, requestedMaxRefreshRate, requestedMinimalPostProcessing, + inTraversal); } @Override diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java index 67779a2e16854..8095919ded082 100644 --- a/services/core/java/com/android/server/display/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/DisplayModeDirector.java @@ -922,6 +922,11 @@ public class DisplayModeDirector { // It votes [MIN_REFRESH_RATE, Float.POSITIVE_INFINITY] public static final int PRIORITY_USER_SETTING_MIN_REFRESH_RATE = 2; + // APP_REQUEST_MAX_REFRESH_RATE is used to for internal apps to limit the refresh + // rate in certain cases, mostly to preserve power. + // It votes to [0, APP_REQUEST_MAX_REFRESH_RATE]. + public static final int PRIORITY_APP_REQUEST_MAX_REFRESH_RATE = 3; + // We split the app request into different priorities in case we can satisfy one desire // without the other. @@ -930,19 +935,19 @@ public class DisplayModeDirector { // @see android.view.WindowManager.LayoutParams#preferredDisplayModeId // System also forces some apps like denylisted app to run at a lower refresh rate. // @see android.R.array#config_highRefreshRateBlacklist - public static final int PRIORITY_APP_REQUEST_REFRESH_RATE = 3; - public static final int PRIORITY_APP_REQUEST_SIZE = 4; + public static final int PRIORITY_APP_REQUEST_REFRESH_RATE = 4; + public static final int PRIORITY_APP_REQUEST_SIZE = 5; // 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 = 5; + public static final int PRIORITY_USER_SETTING_PEAK_REFRESH_RATE = 6; // LOW_POWER_MODE force display to [0, 60HZ] if Settings.Global.LOW_POWER_MODE is on. - public static final int PRIORITY_LOW_POWER_MODE = 6; + public static final int PRIORITY_LOW_POWER_MODE = 7; // 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 = 7; + public static final int PRIORITY_UDFPS = 8; // 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. @@ -953,7 +958,7 @@ public class DisplayModeDirector { // The cutoff for the app request refresh rate range. Votes with priorities lower than this // value will not be considered when constructing the app request refresh rate range. public static final int APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF = - PRIORITY_APP_REQUEST_REFRESH_RATE; + PRIORITY_APP_REQUEST_MAX_REFRESH_RATE; /** * A value signifying an invalid width or height in a vote. @@ -997,6 +1002,8 @@ public class DisplayModeDirector { return "PRIORITY_FLICKER"; 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_REFRESH_RATE: return "PRIORITY_APP_REQUEST_REFRESH_RATE"; case PRIORITY_APP_REQUEST_SIZE: @@ -1182,14 +1189,17 @@ public class DisplayModeDirector { final class AppRequestObserver { private final SparseArray mAppRequestedModeByDisplay; + private final SparseArray mAppPreferredMaxRefreshRateByDisplay; AppRequestObserver() { mAppRequestedModeByDisplay = new SparseArray<>(); + mAppPreferredMaxRefreshRateByDisplay = new SparseArray<>(); } - public void setAppRequestedMode(int displayId, int modeId) { + public void setAppRequest(int displayId, int modeId, float requestedMaxRefreshRate) { synchronized (mLock) { setAppRequestedModeLocked(displayId, modeId); + setAppPreferredMaxRefreshRateLocked(displayId, requestedMaxRefreshRate); } } @@ -1217,6 +1227,29 @@ public class DisplayModeDirector { updateVoteLocked(displayId, Vote.PRIORITY_APP_REQUEST_SIZE, sizeVote); } + private void setAppPreferredMaxRefreshRateLocked(int displayId, + float requestedMaxRefreshRate) { + final Vote vote; + final Float requestedMaxRefreshRateVote = + requestedMaxRefreshRate > 0 + ? new Float(requestedMaxRefreshRate) : null; + if (Objects.equals(requestedMaxRefreshRateVote, + mAppPreferredMaxRefreshRateByDisplay.get(displayId))) { + return; + } + + if (requestedMaxRefreshRate > 0) { + mAppPreferredMaxRefreshRateByDisplay.put(displayId, requestedMaxRefreshRateVote); + vote = Vote.forRefreshRates(0, requestedMaxRefreshRate); + } else { + mAppPreferredMaxRefreshRateByDisplay.remove(displayId); + vote = null; + } + synchronized (mLock) { + updateVoteLocked(displayId, Vote.PRIORITY_APP_REQUEST_MAX_REFRESH_RATE, vote); + } + } + private Display.Mode findModeByIdLocked(int displayId, int modeId) { Display.Mode[] modes = mSupportedModesByDisplay.get(displayId); if (modes == null) { @@ -1238,6 +1271,12 @@ public class DisplayModeDirector { final Display.Mode mode = mAppRequestedModeByDisplay.valueAt(i); pw.println(" " + id + " -> " + mode); } + pw.println(" mAppPreferredMaxRefreshRateByDisplay:"); + for (int i = 0; i < mAppPreferredMaxRefreshRateByDisplay.size(); i++) { + final int id = mAppPreferredMaxRefreshRateByDisplay.keyAt(i); + final Float refreshRate = mAppPreferredMaxRefreshRateByDisplay.valueAt(i); + pw.println(" " + id + " -> " + refreshRate); + } } } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index a10847876ab6a..ec79fd5617f54 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -901,6 +901,14 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp && preferredModeId != 0) { mTmpApplySurfaceChangesTransactionState.preferredModeId = preferredModeId; } + + final float preferredMaxRefreshRate = getDisplayPolicy().getRefreshRatePolicy() + .getPreferredMaxRefreshRate(w); + if (mTmpApplySurfaceChangesTransactionState.preferredMaxRefreshRate == 0 + && preferredMaxRefreshRate != 0) { + mTmpApplySurfaceChangesTransactionState.preferredMaxRefreshRate = + preferredMaxRefreshRate; + } } } @@ -4229,6 +4237,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mLastHasContent, mTmpApplySurfaceChangesTransactionState.preferredRefreshRate, mTmpApplySurfaceChangesTransactionState.preferredModeId, + mTmpApplySurfaceChangesTransactionState.preferredMaxRefreshRate, mTmpApplySurfaceChangesTransactionState.preferMinimalPostProcessing, true /* inTraversal, must call performTraversalInTrans... below */); } @@ -4512,12 +4521,13 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } private static final class ApplySurfaceChangesTransactionState { - boolean displayHasContent; - boolean obscured; - boolean syswin; - boolean preferMinimalPostProcessing; - float preferredRefreshRate; - int preferredModeId; + public boolean displayHasContent; + public boolean obscured; + public boolean syswin; + public boolean preferMinimalPostProcessing; + public float preferredRefreshRate; + public int preferredModeId; + public float preferredMaxRefreshRate; void reset() { displayHasContent = false; @@ -4526,6 +4536,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp preferMinimalPostProcessing = false; preferredRefreshRate = 0; preferredModeId = 0; + preferredMaxRefreshRate = 0; } } diff --git a/services/core/java/com/android/server/wm/RefreshRatePolicy.java b/services/core/java/com/android/server/wm/RefreshRatePolicy.java index 26871d130fbf0..b143012581be4 100644 --- a/services/core/java/com/android/server/wm/RefreshRatePolicy.java +++ b/services/core/java/com/android/server/wm/RefreshRatePolicy.java @@ -95,18 +95,10 @@ class RefreshRatePolicy { return 0; } - // If app requests a certain refresh rate or mode, don't override it. if (w.mAttrs.preferredRefreshRate != 0 || w.mAttrs.preferredDisplayModeId != 0) { return w.mAttrs.preferredDisplayModeId; } - final String packageName = w.getOwningPackage(); - - // If app is using Camera, force it to default (lower) refresh rate. - if (mNonHighRefreshRatePackages.contains(packageName)) { - return mLowRefreshRateMode.getModeId(); - } - return 0; } @@ -147,4 +139,29 @@ class RefreshRatePolicy { } return 0; } + + float getPreferredMaxRefreshRate(WindowState w) { + // If app is animating, it's not able to control refresh rate because we want the animation + // to run in default refresh rate. + if (w.isAnimating(TRANSITION | PARENTS)) { + return 0; + } + + // If the app set a preferredDisplayModeId, we ignore the max preferred refresh rate + if (w.mAttrs.preferredDisplayModeId != 0) { + return 0; + } + + if (w.mAttrs.preferredMaxDisplayRefreshRate > 0) { + return w.mAttrs.preferredMaxDisplayRefreshRate; + } + + final String packageName = w.getOwningPackage(); + // If app is using Camera, force it to default (lower) refresh rate. + if (mNonHighRefreshRatePackages.contains(packageName)) { + return mLowRefreshRateMode.getRefreshRate(); + } + + return 0; + } } 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 cda659f05adf0..cd0a6c6535613 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java +++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java @@ -23,10 +23,12 @@ import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REF 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; +import static com.android.server.display.DisplayModeDirector.Vote.INVALID_SIZE; import static com.android.server.display.DisplayModeDirector.Vote.PRIORITY_FLICKER; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyInt; @@ -724,6 +726,164 @@ public class DisplayModeDirectorTest { assertNull(vote); } + @Test + public void testAppRequestMaxRefreshRate() { + // Confirm that the app max request range doesn't include flicker or min refresh rate + // settings but does include everything else. + assertTrue(Vote.PRIORITY_APP_REQUEST_MAX_REFRESH_RATE + >= Vote.APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF); + + DisplayModeDirector director = createDirectorFromFpsRange(60, 90); + SparseArray votes = new SparseArray<>(); + SparseArray> votesByDisplay = new SparseArray<>(); + votesByDisplay.put(DISPLAY_ID, votes); + votes.put(PRIORITY_FLICKER, Vote.forRefreshRates(60, 60)); + director.injectVotesByDisplay(votesByDisplay); + DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); + assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(60); + assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(60); + assertThat(desiredSpecs.appRequestRefreshRateRange.min).isAtMost(60f); + assertThat(desiredSpecs.appRequestRefreshRateRange.max).isAtLeast(90f); + + votes.put(Vote.PRIORITY_USER_SETTING_MIN_REFRESH_RATE, + Vote.forRefreshRates(90, Float.POSITIVE_INFINITY)); + director.injectVotesByDisplay(votesByDisplay); + desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); + assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(90); + assertThat(desiredSpecs.primaryRefreshRateRange.max).isAtLeast(90f); + assertThat(desiredSpecs.appRequestRefreshRateRange.min).isAtMost(60f); + assertThat(desiredSpecs.appRequestRefreshRateRange.max).isAtLeast(90f); + + votes.put(Vote.PRIORITY_APP_REQUEST_MAX_REFRESH_RATE, Vote.forRefreshRates(0, 75)); + director.injectVotesByDisplay(votesByDisplay); + desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); + // TODO(b/183226498): ideally the refresh rate range should be a single value since + // PRIORITY_FLICKER voted, however since PRIORITY_APP_REQUEST_MAX_REFRESH_RATE votes for + // range, PRIORITY_FLICKER is ignored. The same thing would happen with + // PRIORITY_LOW_POWER_MODE so this is not a regression. This will be fixed in a subsequent + // CL. + //assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(75); + assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(75); + //assertThat(desiredSpecs.appRequestRefreshRateRange.min) + // .isWithin(FLOAT_TOLERANCE) + // .of(75); + assertThat(desiredSpecs.appRequestRefreshRateRange.max) + .isWithin(FLOAT_TOLERANCE) + .of(75); + } + + @Test + public void testAppRequestObserver_modeId() { + DisplayModeDirector director = createDirectorFromFpsRange(60, 90); + director.getAppRequestObserver().setAppRequest(DISPLAY_ID, 60, 0); + + Vote appRequestRefreshRate = + director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_REFRESH_RATE); + assertNotNull(appRequestRefreshRate); + assertThat(appRequestRefreshRate.refreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(60); + assertThat(appRequestRefreshRate.refreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(60); + assertThat(appRequestRefreshRate.height).isEqualTo(INVALID_SIZE); + assertThat(appRequestRefreshRate.width).isEqualTo(INVALID_SIZE); + + Vote appRequestSize = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE); + assertNotNull(appRequestSize); + assertThat(appRequestSize.refreshRateRange.min).isZero(); + assertThat(appRequestSize.refreshRateRange.max).isPositiveInfinity(); + assertThat(appRequestSize.height).isEqualTo(1000); + assertThat(appRequestSize.width).isEqualTo(1000); + + Vote appRequestMaxRefreshRate = + director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_MAX_REFRESH_RATE); + assertNull(appRequestMaxRefreshRate); + + director.getAppRequestObserver().setAppRequest(DISPLAY_ID, 90, 0); + + appRequestRefreshRate = + director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_REFRESH_RATE); + assertNotNull(appRequestRefreshRate); + assertThat(appRequestRefreshRate.refreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(90); + assertThat(appRequestRefreshRate.refreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(90); + assertThat(appRequestRefreshRate.height).isEqualTo(INVALID_SIZE); + assertThat(appRequestRefreshRate.width).isEqualTo(INVALID_SIZE); + + appRequestSize = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE); + assertNotNull(appRequestSize); + assertThat(appRequestSize.refreshRateRange.min).isZero(); + assertThat(appRequestSize.refreshRateRange.max).isPositiveInfinity(); + assertThat(appRequestSize.height).isEqualTo(1000); + assertThat(appRequestSize.width).isEqualTo(1000); + + appRequestMaxRefreshRate = + director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_MAX_REFRESH_RATE); + assertNull(appRequestMaxRefreshRate); + } + + @Test + public void testAppRequestObserver_maxRefreshRate() { + DisplayModeDirector director = createDirectorFromFpsRange(60, 90); + director.getAppRequestObserver().setAppRequest(DISPLAY_ID, -1, 90); + Vote appRequestRefreshRate = + director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_REFRESH_RATE); + assertNull(appRequestRefreshRate); + + Vote appRequestSize = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE); + assertNull(appRequestSize); + + Vote appRequestMaxRefreshRate = + director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_MAX_REFRESH_RATE); + assertNotNull(appRequestMaxRefreshRate); + assertThat(appRequestMaxRefreshRate.refreshRateRange.min).isZero(); + assertThat(appRequestMaxRefreshRate.refreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(90); + assertThat(appRequestMaxRefreshRate.height).isEqualTo(INVALID_SIZE); + assertThat(appRequestMaxRefreshRate.width).isEqualTo(INVALID_SIZE); + + director.getAppRequestObserver().setAppRequest(DISPLAY_ID, -1, 60); + appRequestRefreshRate = + director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_REFRESH_RATE); + assertNull(appRequestRefreshRate); + + appRequestSize = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE); + assertNull(appRequestSize); + + appRequestMaxRefreshRate = + director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_MAX_REFRESH_RATE); + assertNotNull(appRequestMaxRefreshRate); + assertThat(appRequestMaxRefreshRate.refreshRateRange.min).isZero(); + assertThat(appRequestMaxRefreshRate.refreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(60); + assertThat(appRequestMaxRefreshRate.height).isEqualTo(INVALID_SIZE); + assertThat(appRequestMaxRefreshRate.width).isEqualTo(INVALID_SIZE); + } + + @Test + public void testAppRequestObserver_modeIdAndMaxRefreshRate() { + DisplayModeDirector director = createDirectorFromFpsRange(60, 90); + director.getAppRequestObserver().setAppRequest(DISPLAY_ID, 60, 90); + + Vote appRequestRefreshRate = + director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_REFRESH_RATE); + assertNotNull(appRequestRefreshRate); + assertThat(appRequestRefreshRate.refreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(60); + assertThat(appRequestRefreshRate.refreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(60); + assertThat(appRequestRefreshRate.height).isEqualTo(INVALID_SIZE); + assertThat(appRequestRefreshRate.width).isEqualTo(INVALID_SIZE); + + Vote appRequestSize = + director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE); + assertNotNull(appRequestSize); + assertThat(appRequestSize.refreshRateRange.min).isZero(); + assertThat(appRequestSize.refreshRateRange.max).isPositiveInfinity(); + assertThat(appRequestSize.height).isEqualTo(1000); + assertThat(appRequestSize.width).isEqualTo(1000); + + Vote appRequestMaxRefreshRate = + director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_MAX_REFRESH_RATE); + assertNotNull(appRequestMaxRefreshRate); + assertThat(appRequestMaxRefreshRate.refreshRateRange.min).isZero(); + assertThat(appRequestMaxRefreshRate.refreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(90); + assertThat(appRequestMaxRefreshRate.height).isEqualTo(INVALID_SIZE); + assertThat(appRequestMaxRefreshRate.width).isEqualTo(INVALID_SIZE); + } + private void assertVoteForRefreshRate(Vote vote, float refreshRate) { assertThat(vote).isNotNull(); final DisplayModeDirector.RefreshRateRange expectedRange = diff --git a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java index ef3c7ae91fedc..20b987de9aa14 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java @@ -71,9 +71,11 @@ public class RefreshRatePolicyTest extends WindowTestsBase { cameraUsingWindow.mAttrs.packageName = "com.android.test"; assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow)); assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE); + assertEquals(0, mPolicy.getPreferredMaxRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE); mPolicy.addNonHighRefreshRatePackage("com.android.test"); - assertEquals(LOW_MODE_ID, mPolicy.getPreferredModeId(cameraUsingWindow)); + assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow)); assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE); + assertEquals(60, mPolicy.getPreferredMaxRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE); mPolicy.removeNonHighRefreshRatePackage("com.android.test"); assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow)); assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE); @@ -109,6 +111,7 @@ public class RefreshRatePolicyTest extends WindowTestsBase { mPolicy.addNonHighRefreshRatePackage("com.android.test"); assertEquals(LOW_MODE_ID, mPolicy.getPreferredModeId(overrideWindow)); assertEquals(0, mPolicy.getPreferredRefreshRate(overrideWindow), FLOAT_TOLERANCE); + assertEquals(0, mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE); } @Test @@ -123,6 +126,7 @@ public class RefreshRatePolicyTest extends WindowTestsBase { mPolicy.addNonHighRefreshRatePackage("com.android.test"); assertEquals(0, mPolicy.getPreferredModeId(overrideWindow)); assertEquals(0, mPolicy.getPreferredRefreshRate(overrideWindow), FLOAT_TOLERANCE); + assertEquals(0, mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE); } @Test @@ -132,13 +136,31 @@ public class RefreshRatePolicyTest extends WindowTestsBase { cameraUsingWindow.mAttrs.packageName = "com.android.test"; mPolicy.addNonHighRefreshRatePackage("com.android.test"); - assertEquals(LOW_MODE_ID, mPolicy.getPreferredModeId(cameraUsingWindow)); + assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow)); assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE); + assertEquals(60, mPolicy.getPreferredMaxRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE); cameraUsingWindow.mActivityRecord.mSurfaceAnimator.startAnimation( cameraUsingWindow.getPendingTransaction(), mock(AnimationAdapter.class), false /* hidden */, ANIMATION_TYPE_APP_TRANSITION); assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow)); assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE); + assertEquals(0, mPolicy.getPreferredMaxRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE); + } + + @Test + public void testAppMaxRefreshRate() { + final WindowState window = createWindow(null, TYPE_BASE_APPLICATION, "window"); + window.mAttrs.preferredMaxDisplayRefreshRate = 60f; + assertEquals(0, mPolicy.getPreferredModeId(window)); + assertEquals(0, mPolicy.getPreferredRefreshRate(window), FLOAT_TOLERANCE); + assertEquals(60, mPolicy.getPreferredMaxRefreshRate(window), FLOAT_TOLERANCE); + + window.mActivityRecord.mSurfaceAnimator.startAnimation( + window.getPendingTransaction(), mock(AnimationAdapter.class), + false /* hidden */, ANIMATION_TYPE_APP_TRANSITION); + assertEquals(0, mPolicy.getPreferredModeId(window)); + assertEquals(0, mPolicy.getPreferredRefreshRate(window), FLOAT_TOLERANCE); + assertEquals(0, mPolicy.getPreferredMaxRefreshRate(window), FLOAT_TOLERANCE); } } From a3139a40119ab88f055f0d359fb900be52eff1b7 Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Tue, 13 Apr 2021 17:02:15 -0700 Subject: [PATCH 2/2] Enable preferredDisplayModeId for frame rate override Route preferredDisplayModeId vote to SurfaceFlinger using the setFrameRate API to frame rate override to be enabled for applications that vote with preferredDisplayModeId. Bug: 183225713 Test: atest RefreshRatePolicyTest Test: atest DisplayModeDirectorTest Test: atest FrameRateSelectionPriorityTests Test: atest DisplayTest Test: Launch an app that sets preferredDisplayModeId and observe refresh rate Change-Id: I0f9110adc83eb1c7c994440e6b40f3e1036176e6 --- .../server/display/DisplayModeDirector.java | 209 +++++--- .../android/server/wm/RefreshRatePolicy.java | 16 +- .../com/android/server/wm/WindowState.java | 15 +- .../display/DisplayModeDirectorTest.java | 467 +++++++++++++++--- .../wm/FrameRateSelectionPriorityTests.java | 47 +- .../android/server/wm/StubTransaction.java | 2 +- 6 files changed, 587 insertions(+), 169 deletions(-) diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java index 8095919ded082..997f0e5aa70cd 100644 --- a/services/core/java/com/android/server/display/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/DisplayModeDirector.java @@ -198,6 +198,8 @@ public class DisplayModeDirector { public float maxRefreshRate; public int width; public int height; + public boolean disableRefreshRateSwitching; + public float baseModeRefreshRate; VoteSummary() { reset(); @@ -208,6 +210,8 @@ public class DisplayModeDirector { maxRefreshRate = Float.POSITIVE_INFINITY; width = Vote.INVALID_SIZE; height = Vote.INVALID_SIZE; + disableRefreshRateSwitching = false; + baseModeRefreshRate = 0f; } } @@ -229,13 +233,20 @@ public class DisplayModeDirector { // For refresh rates, just use the tightest bounds of all the votes summary.minRefreshRate = Math.max(summary.minRefreshRate, vote.refreshRateRange.min); summary.maxRefreshRate = Math.min(summary.maxRefreshRate, vote.refreshRateRange.max); - // For display size, use only the first vote we come across (i.e. the highest - // priority vote that includes the width / height). + // For display size, disable refresh rate switching and base mode refresh rate use only + // the first vote we come across (i.e. the highest priority vote that includes the + // attribute). if (summary.height == Vote.INVALID_SIZE && summary.width == Vote.INVALID_SIZE && vote.height > 0 && vote.width > 0) { summary.width = vote.width; summary.height = vote.height; } + if (!summary.disableRefreshRateSwitching && vote.disableRefreshRateSwitching) { + summary.disableRefreshRateSwitching = true; + } + if (summary.baseModeRefreshRate == 0f && vote.baseModeRefreshRate > 0f) { + summary.baseModeRefreshRate = vote.baseModeRefreshRate; + } } } @@ -260,13 +271,14 @@ public class DisplayModeDirector { return new DesiredDisplayModeSpecs(); } - int[] availableModes = new int[]{defaultMode.getModeId()}; + ArrayList availableModes = new ArrayList<>(); + availableModes.add(defaultMode); VoteSummary primarySummary = new VoteSummary(); int lowestConsideredPriority = Vote.MIN_PRIORITY; int highestConsideredPriority = Vote.MAX_PRIORITY; if (mAlwaysRespectAppRequest) { - lowestConsideredPriority = Vote.PRIORITY_APP_REQUEST_REFRESH_RATE; + lowestConsideredPriority = Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE; highestConsideredPriority = Vote.PRIORITY_APP_REQUEST_SIZE; } @@ -286,16 +298,19 @@ public class DisplayModeDirector { } availableModes = filterModes(modes, primarySummary); - if (availableModes.length > 0) { + if (!availableModes.isEmpty()) { if (mLoggingEnabled) { - Slog.w(TAG, "Found available modes=" + Arrays.toString(availableModes) + Slog.w(TAG, "Found available modes=" + availableModes + " with lowest priority considered " + Vote.priorityToString(lowestConsideredPriority) + " and constraints: " + "width=" + primarySummary.width + ", height=" + primarySummary.height + ", minRefreshRate=" + primarySummary.minRefreshRate - + ", maxRefreshRate=" + primarySummary.maxRefreshRate); + + ", maxRefreshRate=" + primarySummary.maxRefreshRate + + ", disableRefreshRateSwitching=" + + primarySummary.disableRefreshRateSwitching + + ", baseModeRefreshRate=" + primarySummary.baseModeRefreshRate); } break; } @@ -307,7 +322,10 @@ public class DisplayModeDirector { + "width=" + primarySummary.width + ", height=" + primarySummary.height + ", minRefreshRate=" + primarySummary.minRefreshRate - + ", maxRefreshRate=" + primarySummary.maxRefreshRate); + + ", maxRefreshRate=" + primarySummary.maxRefreshRate + + ", disableRefreshRateSwitching=" + + primarySummary.disableRefreshRateSwitching + + ", baseModeRefreshRate=" + primarySummary.baseModeRefreshRate); } // If we haven't found anything with the current set of votes, drop the @@ -332,26 +350,38 @@ public class DisplayModeDirector { appRequestSummary.maxRefreshRate)); } - int baseModeId = INVALID_DISPLAY_MODE_ID; + // Select the base mode id based on the base mode refresh rate, if available, since this + // will be the mode id the app voted for. + Display.Mode baseMode = null; + for (Display.Mode availableMode : availableModes) { + if (primarySummary.baseModeRefreshRate + >= availableMode.getRefreshRate() - FLOAT_TOLERANCE + && primarySummary.baseModeRefreshRate + <= availableMode.getRefreshRate() + FLOAT_TOLERANCE) { + baseMode = availableMode; + } + } // Select the default mode if available. This is important because SurfaceFlinger // can do only seamless switches by default. Some devices (e.g. TV) don't support // seamless switching so the mode we select here won't be changed. - for (int availableMode : availableModes) { - if (availableMode == defaultMode.getModeId()) { - baseModeId = defaultMode.getModeId(); - break; + if (baseMode == null) { + for (Display.Mode availableMode : availableModes) { + if (availableMode.getModeId() == defaultMode.getModeId()) { + baseMode = defaultMode; + break; + } } } // If the application requests a display mode by setting // LayoutParams.preferredDisplayModeId, it will be the only available mode and it'll // be stored as baseModeId. - if (baseModeId == INVALID_DISPLAY_MODE_ID && availableModes.length > 0) { - baseModeId = availableModes[0]; + if (baseMode == null && !availableModes.isEmpty()) { + baseMode = availableModes.get(0); } - if (baseModeId == INVALID_DISPLAY_MODE_ID) { + if (baseMode == null) { Slog.w(TAG, "Can't find a set of allowed modes which satisfies the votes. Falling" + " back to the default mode. Display = " + displayId + ", votes = " + votes + ", supported modes = " + Arrays.toString(modes)); @@ -363,31 +393,19 @@ public class DisplayModeDirector { new RefreshRateRange(fps, fps)); } - if (mModeSwitchingType == DisplayManager.SWITCHING_TYPE_NONE) { - Display.Mode baseMode = null; - for (Display.Mode mode : modes) { - if (mode.getModeId() == baseModeId) { - baseMode = mode; - break; - } - } - if (baseMode == null) { - // This should never happen. - throw new IllegalStateException( - "The base mode with id " + baseModeId - + " is not in the list of supported modes."); - } + if (mModeSwitchingType == DisplayManager.SWITCHING_TYPE_NONE + || primarySummary.disableRefreshRateSwitching) { float fps = baseMode.getRefreshRate(); - return new DesiredDisplayModeSpecs(baseModeId, - /*allowGroupSwitching */ false, - new RefreshRateRange(fps, fps), - new RefreshRateRange(fps, fps)); + primarySummary.minRefreshRate = primarySummary.maxRefreshRate = fps; + if (mModeSwitchingType == DisplayManager.SWITCHING_TYPE_NONE) { + appRequestSummary.minRefreshRate = appRequestSummary.maxRefreshRate = fps; + } } boolean allowGroupSwitching = mModeSwitchingType == DisplayManager.SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS; - return new DesiredDisplayModeSpecs(baseModeId, + return new DesiredDisplayModeSpecs(baseMode.getModeId(), allowGroupSwitching, new RefreshRateRange( primarySummary.minRefreshRate, primarySummary.maxRefreshRate), @@ -396,8 +414,10 @@ public class DisplayModeDirector { } } - private int[] filterModes(Display.Mode[] supportedModes, VoteSummary summary) { + private ArrayList filterModes(Display.Mode[] supportedModes, + VoteSummary summary) { ArrayList availableModes = new ArrayList<>(); + boolean missingBaseModeRefreshRate = summary.baseModeRefreshRate > 0f; for (Display.Mode mode : supportedModes) { if (mode.getPhysicalWidth() != summary.width || mode.getPhysicalHeight() != summary.height) { @@ -426,13 +446,16 @@ public class DisplayModeDirector { continue; } availableModes.add(mode); + if (mode.getRefreshRate() >= summary.baseModeRefreshRate - FLOAT_TOLERANCE + && mode.getRefreshRate() <= summary.baseModeRefreshRate + FLOAT_TOLERANCE) { + missingBaseModeRefreshRate = false; + } } - final int size = availableModes.size(); - int[] availableModeIds = new int[size]; - for (int i = 0; i < size; i++) { - availableModeIds[i] = availableModes.get(i).getModeId(); + if (missingBaseModeRefreshRate) { + return new ArrayList<>(); } - return availableModeIds; + + return availableModes; } /** @@ -912,11 +935,10 @@ public class DisplayModeDirector { // by all other considerations. It acts to set a default frame rate for a device. public static final int PRIORITY_DEFAULT_REFRESH_RATE = 0; - // FLICKER votes for a single refresh rate like [60,60], [90,90] or null. - // If the higher 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 = 1; + // PRIORITY_FLICKER_REFRESH_RATE votes for a single refresh rate like [60,60], [90,90] or + // null. It is used to set a preferred refresh rate value in case the higher priority votes + // result is a range. + public static final int PRIORITY_FLICKER_REFRESH_RATE = 1; // SETTING_MIN_REFRESH_RATE is used to propose a lower bound of display refresh rate. // It votes [MIN_REFRESH_RATE, Float.POSITIVE_INFINITY] @@ -933,9 +955,14 @@ public class DisplayModeDirector { // Application can specify preferred refresh rate with below attrs. // @see android.view.WindowManager.LayoutParams#preferredRefreshRate // @see android.view.WindowManager.LayoutParams#preferredDisplayModeId - // System also forces some apps like denylisted app to run at a lower refresh rate. + // These translates into votes for the base mode refresh rate and resolution to be + // used by SurfaceFlinger as the policy of choosing the display mode. The system also + // forces some apps like denylisted app to run at a lower refresh rate. // @see android.R.array#config_highRefreshRateBlacklist - public static final int PRIORITY_APP_REQUEST_REFRESH_RATE = 4; + // 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; // SETTING_PEAK_REFRESH_RATE has a high priority and will restrict the bounds of the rest @@ -945,9 +972,15 @@ public class DisplayModeDirector { // 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; + // 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; + // 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 = 8; + public static final int PRIORITY_UDFPS = 9; // 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. @@ -978,34 +1011,64 @@ public class DisplayModeDirector { */ public final RefreshRateRange refreshRateRange; + /** + * Whether refresh rate switching should be disabled (i.e. the refresh rate range is + * a single value). + */ + public final boolean disableRefreshRateSwitching; + + /** + * The base mode refresh rate to be used for this display. This would be used when deciding + * the base mode id. + */ + public final float baseModeRefreshRate; + public static Vote forRefreshRates(float minRefreshRate, float maxRefreshRate) { - return new Vote(INVALID_SIZE, INVALID_SIZE, minRefreshRate, maxRefreshRate); + return new Vote(INVALID_SIZE, INVALID_SIZE, minRefreshRate, maxRefreshRate, + minRefreshRate == maxRefreshRate, 0f); } public static Vote forSize(int width, int height) { - return new Vote(width, height, 0f, Float.POSITIVE_INFINITY); + return new Vote(width, height, 0f, Float.POSITIVE_INFINITY, false, + 0f); + } + + public static Vote forDisableRefreshRateSwitching() { + return new Vote(INVALID_SIZE, INVALID_SIZE, 0f, Float.POSITIVE_INFINITY, true, + 0f); + } + + public static Vote forBaseModeRefreshRate(float baseModeRefreshRate) { + return new Vote(INVALID_SIZE, INVALID_SIZE, 0f, Float.POSITIVE_INFINITY, false, + baseModeRefreshRate); } private Vote(int width, int height, - float minRefreshRate, float maxRefreshRate) { + float minRefreshRate, float maxRefreshRate, + boolean disableRefreshRateSwitching, + float baseModeRefreshRate) { this.width = width; this.height = height; this.refreshRateRange = new RefreshRateRange(minRefreshRate, maxRefreshRate); + this.disableRefreshRateSwitching = disableRefreshRateSwitching; + this.baseModeRefreshRate = baseModeRefreshRate; } public static String priorityToString(int priority) { switch (priority) { case PRIORITY_DEFAULT_REFRESH_RATE: return "PRIORITY_DEFAULT_REFRESH_RATE"; - case PRIORITY_FLICKER: - return "PRIORITY_FLICKER"; + 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_REFRESH_RATE: - return "PRIORITY_APP_REQUEST_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: @@ -1025,7 +1088,9 @@ public class DisplayModeDirector { return "Vote{" + "width=" + width + ", height=" + height + ", minRefreshRate=" + refreshRateRange.min - + ", maxRefreshRate=" + refreshRateRange.max + "}"; + + ", maxRefreshRate=" + refreshRateRange.max + + ", disableRefreshRateSwitching=" + disableRefreshRateSwitching + + ", baseModeRefreshRate=" + baseModeRefreshRate + "}"; } } @@ -1209,21 +1274,22 @@ public class DisplayModeDirector { return; } - final Vote refreshRateVote; + final Vote baseModeRefreshRateVote; final Vote sizeVote; if (requestedMode != null) { mAppRequestedModeByDisplay.put(displayId, requestedMode); - float refreshRate = requestedMode.getRefreshRate(); - refreshRateVote = Vote.forRefreshRates(refreshRate, refreshRate); + baseModeRefreshRateVote = + Vote.forBaseModeRefreshRate(requestedMode.getRefreshRate()); sizeVote = Vote.forSize(requestedMode.getPhysicalWidth(), requestedMode.getPhysicalHeight()); } else { mAppRequestedModeByDisplay.remove(displayId); - refreshRateVote = null; + baseModeRefreshRateVote = null; sizeVote = null; } - updateVoteLocked(displayId, Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, refreshRateVote); + updateVoteLocked(displayId, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, + baseModeRefreshRateVote); updateVoteLocked(displayId, Vote.PRIORITY_APP_REQUEST_SIZE, sizeVote); } @@ -1525,7 +1591,8 @@ public class DisplayModeDirector { updateSensorStatus(); if (!changeable) { // Revoke previous vote from BrightnessObserver - updateVoteLocked(Vote.PRIORITY_FLICKER, null); + updateVoteLocked(Vote.PRIORITY_FLICKER_REFRESH_RATE, null); + updateVoteLocked(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH, null); } } } @@ -1773,7 +1840,8 @@ public class DisplayModeDirector { return false; } private void onBrightnessChangedLocked() { - Vote vote = null; + Vote refreshRateVote = null; + Vote refreshRateSwitchingVote = null; if (mBrightness < 0) { // Either the setting isn't available or we shouldn't be observing yet anyways. @@ -1783,20 +1851,25 @@ public class DisplayModeDirector { boolean insideLowZone = hasValidLowZone() && isInsideLowZone(mBrightness, mAmbientLux); if (insideLowZone) { - vote = Vote.forRefreshRates(mRefreshRateInLowZone, mRefreshRateInLowZone); + refreshRateVote = + Vote.forRefreshRates(mRefreshRateInLowZone, mRefreshRateInLowZone); + refreshRateSwitchingVote = Vote.forDisableRefreshRateSwitching(); } boolean insideHighZone = hasValidHighZone() && isInsideHighZone(mBrightness, mAmbientLux); if (insideHighZone) { - vote = Vote.forRefreshRates(mRefreshRateInHighZone, mRefreshRateInHighZone); + refreshRateVote = + Vote.forRefreshRates(mRefreshRateInHighZone, mRefreshRateInHighZone); + refreshRateSwitchingVote = Vote.forDisableRefreshRateSwitching(); } if (mLoggingEnabled) { Slog.d(TAG, "Display brightness " + mBrightness + ", ambient lux " + mAmbientLux - + ", Vote " + vote); + + ", Vote " + refreshRateVote); } - updateVoteLocked(Vote.PRIORITY_FLICKER, vote); + updateVoteLocked(Vote.PRIORITY_FLICKER_REFRESH_RATE, refreshRateVote); + updateVoteLocked(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH, refreshRateSwitchingVote); } private boolean hasValidLowZone() { diff --git a/services/core/java/com/android/server/wm/RefreshRatePolicy.java b/services/core/java/com/android/server/wm/RefreshRatePolicy.java index b143012581be4..deaf611446bc6 100644 --- a/services/core/java/com/android/server/wm/RefreshRatePolicy.java +++ b/services/core/java/com/android/server/wm/RefreshRatePolicy.java @@ -20,6 +20,7 @@ import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS; import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION; import android.util.ArraySet; +import android.view.Display; import android.view.Display.Mode; import android.view.DisplayInfo; @@ -137,6 +138,19 @@ class RefreshRatePolicy { if (mHighRefreshRateDenylist.isDenylisted(packageName)) { return mLowRefreshRateMode.getRefreshRate(); } + + final int preferredModeId = getPreferredModeId(w); + if (preferredModeId > 0) { + DisplayInfo info = w.getDisplayInfo(); + if (info != null) { + for (Display.Mode mode : info.supportedModes) { + if (preferredModeId == mode.getModeId()) { + return mode.getRefreshRate(); + } + } + } + } + return 0; } @@ -147,7 +161,7 @@ class RefreshRatePolicy { return 0; } - // If the app set a preferredDisplayModeId, we ignore the max preferred refresh rate + // If app requests a certain refresh rate or mode, don't override it. if (w.mAttrs.preferredDisplayModeId != 0) { return 0; } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index b40223f6eed79..70122780edfd9 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -751,11 +751,11 @@ class WindowState extends WindowContainer implements WindowManagerP int mFrameRateSelectionPriority = RefreshRatePolicy.LAYER_PRIORITY_UNSET; /** - * This is the frame rate which is passed to SurfaceFlinger if the window is part of the - * high refresh rate deny list. The variable is cached, so we do not send too many updates to - * SF. + * This is the frame rate which is passed to SurfaceFlinger if the window set a + * preferredDisplayModeId or is part of the high refresh rate deny list. + * The variable is cached, so we do not send too many updates to SF. */ - float mDenyListFrameRate = 0f; + float mAppPreferredFrameRate = 0f; static final int BLAST_TIMEOUT_DURATION = 5000; /* milliseconds */ @@ -5406,10 +5406,11 @@ class WindowState extends WindowContainer implements WindowManagerP } final float refreshRate = refreshRatePolicy.getPreferredRefreshRate(this); - if (mDenyListFrameRate != refreshRate) { - mDenyListFrameRate = refreshRate; + if (mAppPreferredFrameRate != refreshRate) { + mAppPreferredFrameRate = refreshRate; getPendingTransaction().setFrameRate( - mSurfaceControl, mDenyListFrameRate, Surface.FRAME_RATE_COMPATIBILITY_EXACT); + mSurfaceControl, mAppPreferredFrameRate, + Surface.FRAME_RATE_COMPATIBILITY_EXACT, Surface.CHANGE_FRAME_RATE_ALWAYS); } } 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 cd0a6c6535613..fb2db2259c7e4 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java +++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java @@ -24,7 +24,6 @@ import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_R import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE; import static com.android.server.display.DisplayModeDirector.Vote.INVALID_SIZE; -import static com.android.server.display.DisplayModeDirector.Vote.PRIORITY_FLICKER; import static com.google.common.truth.Truth.assertThat; @@ -139,7 +138,14 @@ public class DisplayModeDirectorTest { } } assertThat(defaultMode).isNotNull(); + return createDirectorFromModeArray(modes, defaultMode); + } + private DisplayModeDirector createDirectorFromModeArray(Display.Mode[] modes, + Display.Mode defaultMode) { + DisplayModeDirector director = + new DisplayModeDirector(mContext, mHandler, mInjector); + director.setLoggingEnabled(true); SparseArray supportedModesByDisplay = new SparseArray<>(); supportedModesByDisplay.put(DISPLAY_ID, modes); director.injectSupportedModesByDisplay(supportedModesByDisplay); @@ -220,8 +226,9 @@ public class DisplayModeDirectorTest { votesByDisplay.put(DISPLAY_ID, votes); float error = FLOAT_TOLERANCE / 4; votes.put(Vote.PRIORITY_USER_SETTING_PEAK_REFRESH_RATE, Vote.forRefreshRates(0, 60)); - votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forRefreshRates(60 + error, 60 + error)); - votes.put(Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, + votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, + Vote.forRefreshRates(60 + error, 60 + error)); + votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, Vote.forRefreshRates(60 - error, 60 - error)); director.injectVotesByDisplay(votesByDisplay); DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); @@ -232,44 +239,159 @@ public class DisplayModeDirectorTest { } @Test - public void testFlickerHasLowerPriorityThanUser() { - assertTrue(PRIORITY_FLICKER < Vote.PRIORITY_APP_REQUEST_REFRESH_RATE); - assertTrue(PRIORITY_FLICKER < Vote.PRIORITY_APP_REQUEST_SIZE); + public void testFlickerHasLowerPriorityThanUserAndRangeIsSingle() { + assertTrue(Vote.PRIORITY_FLICKER_REFRESH_RATE + < Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE); + assertTrue(Vote.PRIORITY_FLICKER_REFRESH_RATE + < Vote.PRIORITY_APP_REQUEST_SIZE); - DisplayModeDirector director = createDirectorFromFpsRange(60, 90); + assertTrue(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH + > Vote.PRIORITY_LOW_POWER_MODE); + + Display.Mode[] modes = new Display.Mode[4]; + modes[0] = new Display.Mode( + /*modeId=*/1, /*width=*/1000, /*height=*/1000, 60); + modes[1] = new Display.Mode( + /*modeId=*/2, /*width=*/2000, /*height=*/2000, 60); + modes[2] = new Display.Mode( + /*modeId=*/3, /*width=*/1000, /*height=*/1000, 90); + modes[3] = new Display.Mode( + /*modeId=*/4, /*width=*/2000, /*height=*/2000, 90); + + DisplayModeDirector director = createDirectorFromModeArray(modes, modes[0]); SparseArray votes = new SparseArray<>(); SparseArray> votesByDisplay = new SparseArray<>(); votesByDisplay.put(DISPLAY_ID, votes); - votes.put(Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, Vote.forRefreshRates(60, 90)); - votes.put(PRIORITY_FLICKER, Vote.forRefreshRates(60, 60)); + Display.Mode appRequestedMode = modes[1]; + votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, + Vote.forBaseModeRefreshRate(appRequestedMode.getRefreshRate())); + votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(appRequestedMode.getPhysicalWidth(), + appRequestedMode.getPhysicalHeight())); + votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE, Vote.forRefreshRates(60, 60)); + votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH, Vote.forDisableRefreshRateSwitching()); director.injectVotesByDisplay(votesByDisplay); DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); + assertThat(desiredSpecs.baseModeId).isEqualTo(2); + assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(60); + assertThat(desiredSpecs.primaryRefreshRateRange.max) + .isWithin(FLOAT_TOLERANCE).of(desiredSpecs.primaryRefreshRateRange.min); + + votes.clear(); + appRequestedMode = modes[3]; + votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, + Vote.forBaseModeRefreshRate(appRequestedMode.getRefreshRate())); + votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(appRequestedMode.getPhysicalWidth(), + appRequestedMode.getPhysicalHeight())); + votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE, Vote.forRefreshRates(90, 90)); + votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH, Vote.forDisableRefreshRateSwitching()); + director.injectVotesByDisplay(votesByDisplay); + desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); + assertThat(desiredSpecs.baseModeId).isEqualTo(4); + assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(90); + assertThat(desiredSpecs.primaryRefreshRateRange.max) + .isWithin(FLOAT_TOLERANCE).of(desiredSpecs.primaryRefreshRateRange.min); + + votes.clear(); + appRequestedMode = modes[3]; + votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, + Vote.forBaseModeRefreshRate(appRequestedMode.getRefreshRate())); + votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(appRequestedMode.getPhysicalWidth(), + appRequestedMode.getPhysicalHeight())); + votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE, Vote.forRefreshRates(60, 60)); + votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH, Vote.forDisableRefreshRateSwitching()); + director.injectVotesByDisplay(votesByDisplay); + desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); + assertThat(desiredSpecs.baseModeId).isEqualTo(4); + assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(90); + assertThat(desiredSpecs.primaryRefreshRateRange.max) + .isWithin(FLOAT_TOLERANCE).of(desiredSpecs.primaryRefreshRateRange.min); + + votes.clear(); + appRequestedMode = modes[1]; + votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, + Vote.forBaseModeRefreshRate(appRequestedMode.getRefreshRate())); + votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(appRequestedMode.getPhysicalWidth(), + appRequestedMode.getPhysicalHeight())); + votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE, Vote.forRefreshRates(90, 90)); + votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH, Vote.forDisableRefreshRateSwitching()); + director.injectVotesByDisplay(votesByDisplay); + desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); + assertThat(desiredSpecs.baseModeId).isEqualTo(2); + assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(60); + assertThat(desiredSpecs.primaryRefreshRateRange.max) + .isWithin(FLOAT_TOLERANCE).of(desiredSpecs.primaryRefreshRateRange.min); + } + + @Test + public void testLPMHasHigherPriorityThanUser() { + assertTrue(Vote.PRIORITY_LOW_POWER_MODE > Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE); + assertTrue(Vote.PRIORITY_LOW_POWER_MODE > Vote.PRIORITY_APP_REQUEST_SIZE); + + + Display.Mode[] modes = new Display.Mode[4]; + modes[0] = new Display.Mode( + /*modeId=*/1, /*width=*/1000, /*height=*/1000, 60); + modes[1] = new Display.Mode( + /*modeId=*/2, /*width=*/2000, /*height=*/2000, 60); + modes[2] = new Display.Mode( + /*modeId=*/3, /*width=*/1000, /*height=*/1000, 90); + modes[3] = new Display.Mode( + /*modeId=*/4, /*width=*/2000, /*height=*/2000, 90); + + DisplayModeDirector director = createDirectorFromModeArray(modes, modes[0]); + SparseArray votes = new SparseArray<>(); + SparseArray> votesByDisplay = new SparseArray<>(); + votesByDisplay.put(DISPLAY_ID, votes); + Display.Mode appRequestedMode = modes[1]; + votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, + Vote.forBaseModeRefreshRate(appRequestedMode.getRefreshRate())); + votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(appRequestedMode.getPhysicalWidth(), + appRequestedMode.getPhysicalHeight())); + votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRefreshRates(60, 60)); + director.injectVotesByDisplay(votesByDisplay); + DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); + assertThat(desiredSpecs.baseModeId).isEqualTo(2); assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(60); assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(60); votes.clear(); - votes.put(Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, Vote.forRefreshRates(60, 90)); - votes.put(PRIORITY_FLICKER, Vote.forRefreshRates(90, 90)); + appRequestedMode = modes[3]; + votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, + Vote.forBaseModeRefreshRate(appRequestedMode.getRefreshRate())); + votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(appRequestedMode.getPhysicalWidth(), + appRequestedMode.getPhysicalHeight())); + votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRefreshRates(90, 90)); director.injectVotesByDisplay(votesByDisplay); desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); + assertThat(desiredSpecs.baseModeId).isEqualTo(4); assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(90); assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(90); votes.clear(); - votes.put(Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, Vote.forRefreshRates(90, 90)); - votes.put(PRIORITY_FLICKER, Vote.forRefreshRates(60, 60)); - director.injectVotesByDisplay(votesByDisplay); - desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); - assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(90); - assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(90); - - votes.clear(); - votes.put(Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, Vote.forRefreshRates(60, 60)); - votes.put(PRIORITY_FLICKER, Vote.forRefreshRates(90, 90)); + appRequestedMode = modes[3]; + votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, + Vote.forBaseModeRefreshRate(appRequestedMode.getRefreshRate())); + votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(appRequestedMode.getPhysicalWidth(), + appRequestedMode.getPhysicalHeight())); + votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRefreshRates(60, 60)); director.injectVotesByDisplay(votesByDisplay); desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); + assertThat(desiredSpecs.baseModeId).isEqualTo(2); assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(60); assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(60); + + votes.clear(); + appRequestedMode = modes[1]; + votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, + Vote.forBaseModeRefreshRate(appRequestedMode.getRefreshRate())); + votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(appRequestedMode.getPhysicalWidth(), + appRequestedMode.getPhysicalHeight())); + votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRefreshRates(90, 90)); + director.injectVotesByDisplay(votesByDisplay); + desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); + assertThat(desiredSpecs.baseModeId).isEqualTo(4); + assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(90); + assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(90); } @Test @@ -277,19 +399,30 @@ public class DisplayModeDirectorTest { // Confirm that the app request range doesn't include flicker or min refresh rate settings, // but does include everything else. assertTrue( - PRIORITY_FLICKER < Vote.APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF); + Vote.PRIORITY_FLICKER_REFRESH_RATE + < Vote.APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF); assertTrue(Vote.PRIORITY_USER_SETTING_MIN_REFRESH_RATE < Vote.APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF); - assertTrue(Vote.PRIORITY_APP_REQUEST_REFRESH_RATE + assertTrue(Vote.PRIORITY_USER_SETTING_PEAK_REFRESH_RATE >= Vote.APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF); - DisplayModeDirector director = createDirectorFromFpsRange(60, 90); + Display.Mode[] modes = new Display.Mode[3]; + modes[0] = new Display.Mode( + /*modeId=*/60, /*width=*/1000, /*height=*/1000, 60); + modes[1] = new Display.Mode( + /*modeId=*/75, /*width=*/2000, /*height=*/2000, 75); + modes[2] = new Display.Mode( + /*modeId=*/90, /*width=*/1000, /*height=*/1000, 90); + + DisplayModeDirector director = createDirectorFromModeArray(modes, modes[0]); SparseArray votes = new SparseArray<>(); SparseArray> votesByDisplay = new SparseArray<>(); votesByDisplay.put(DISPLAY_ID, votes); - votes.put(PRIORITY_FLICKER, Vote.forRefreshRates(60, 60)); + votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE, Vote.forRefreshRates(60, 60)); + votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH, Vote.forDisableRefreshRateSwitching()); director.injectVotesByDisplay(votesByDisplay); DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); + assertThat(desiredSpecs.baseModeId).isEqualTo(60); assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(60); assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(60); assertThat(desiredSpecs.appRequestRefreshRateRange.min).isAtMost(60f); @@ -299,22 +432,24 @@ public class DisplayModeDirectorTest { Vote.forRefreshRates(90, Float.POSITIVE_INFINITY)); director.injectVotesByDisplay(votesByDisplay); desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); + assertThat(desiredSpecs.baseModeId).isEqualTo(90); assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(90); assertThat(desiredSpecs.primaryRefreshRateRange.max).isAtLeast(90f); assertThat(desiredSpecs.appRequestRefreshRateRange.min).isAtMost(60f); assertThat(desiredSpecs.appRequestRefreshRateRange.max).isAtLeast(90f); - votes.put(Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, Vote.forRefreshRates(75, 75)); + Display.Mode appRequestedMode = modes[1]; + votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, + Vote.forBaseModeRefreshRate(appRequestedMode.getRefreshRate())); + votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(appRequestedMode.getPhysicalWidth(), + appRequestedMode.getPhysicalHeight())); director.injectVotesByDisplay(votesByDisplay); desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); + assertThat(desiredSpecs.baseModeId).isEqualTo(75); assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(75); assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(75); - assertThat(desiredSpecs.appRequestRefreshRateRange.min) - .isWithin(FLOAT_TOLERANCE) - .of(75); - assertThat(desiredSpecs.appRequestRefreshRateRange.max) - .isWithin(FLOAT_TOLERANCE) - .of(75); + assertThat(desiredSpecs.appRequestRefreshRateRange.min).isAtMost(60f); + assertThat(desiredSpecs.appRequestRefreshRateRange.max).isAtLeast(90f); } void verifySpecsWithRefreshRateSettings(DisplayModeDirector director, float minFps, @@ -376,18 +511,29 @@ public class DisplayModeDirectorTest { @Test public void testVotingWithAlwaysRespectAppRequest() { - DisplayModeDirector director = createDirectorFromFpsRange(50, 90); + Display.Mode[] modes = new Display.Mode[3]; + modes[0] = new Display.Mode( + /*modeId=*/50, /*width=*/1000, /*height=*/1000, 50); + modes[1] = new Display.Mode( + /*modeId=*/60, /*width=*/1000, /*height=*/1000, 60); + modes[2] = new Display.Mode( + /*modeId=*/90, /*width=*/1000, /*height=*/1000, 90); + + DisplayModeDirector director = createDirectorFromModeArray(modes, modes[0]); + + SparseArray votes = new SparseArray<>(); SparseArray> votesByDisplay = new SparseArray<>(); votesByDisplay.put(DISPLAY_ID, votes); - votes.put(PRIORITY_FLICKER, Vote.forRefreshRates(0, 60)); + votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE, Vote.forRefreshRates(0, 60)); votes.put(Vote.PRIORITY_USER_SETTING_MIN_REFRESH_RATE, Vote.forRefreshRates(60, 90)); - votes.put(Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, Vote.forRefreshRates(90, 90)); + Display.Mode appRequestedMode = modes[2]; + votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, + Vote.forBaseModeRefreshRate(appRequestedMode.getRefreshRate())); votes.put(Vote.PRIORITY_USER_SETTING_PEAK_REFRESH_RATE, Vote.forRefreshRates(60, 60)); votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRefreshRates(0, 60)); - - director.injectVotesByDisplay(votesByDisplay); + assertThat(director.shouldAlwaysRespectAppRequestedMode()).isFalse(); DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); @@ -398,8 +544,8 @@ public class DisplayModeDirectorTest { director.setShouldAlwaysRespectAppRequestedMode(true); assertThat(director.shouldAlwaysRespectAppRequestedMode()).isTrue(); desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); - assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(90); - assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(90); + assertThat(desiredSpecs.primaryRefreshRateRange.min).isAtMost(50); + assertThat(desiredSpecs.primaryRefreshRateRange.max).isAtLeast(90); assertThat(desiredSpecs.baseModeId).isEqualTo(90); director.setShouldAlwaysRespectAppRequestedMode(false); @@ -499,7 +645,8 @@ public class DisplayModeDirectorTest { // Inject votes SparseArray votes = new SparseArray<>(); votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(1920, 1080)); - votes.put(Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, Vote.forRefreshRates(50, 50)); + votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, + Vote.forBaseModeRefreshRate(60)); SparseArray> votesByDisplay = new SparseArray<>(); votesByDisplay.put(DISPLAY_ID, votes); director.injectVotesByDisplay(votesByDisplay); @@ -594,14 +741,19 @@ public class DisplayModeDirectorTest { // Sensor reads 20 lux, listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 20 /*lux*/)); - Vote vote = director.getVote(Display.DEFAULT_DISPLAY, PRIORITY_FLICKER); + Vote vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE); assertVoteForRefreshRate(vote, 90 /*fps*/); + vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH); + assertThat(vote).isNotNull(); + assertThat(vote.disableRefreshRateSwitching).isTrue(); setBrightness(125); // Sensor reads 1000 lux, listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 1000 /*lux*/)); - vote = director.getVote(Display.DEFAULT_DISPLAY, PRIORITY_FLICKER); + vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE); + assertThat(vote).isNull(); + vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH); assertThat(vote).isNull(); } @@ -637,15 +789,20 @@ public class DisplayModeDirectorTest { // Sensor reads 2000 lux, listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 2000)); - Vote vote = director.getVote(Display.DEFAULT_DISPLAY, PRIORITY_FLICKER); + Vote vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE); + assertThat(vote).isNull(); + vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH); assertThat(vote).isNull(); setBrightness(255); // Sensor reads 9000 lux, listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 9000)); - vote = director.getVote(Display.DEFAULT_DISPLAY, PRIORITY_FLICKER); + vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE); assertVoteForRefreshRate(vote, 60 /*fps*/); + vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH); + assertThat(vote).isNotNull(); + assertThat(vote.disableRefreshRateSwitching).isTrue(); } @Test @@ -733,11 +890,20 @@ public class DisplayModeDirectorTest { assertTrue(Vote.PRIORITY_APP_REQUEST_MAX_REFRESH_RATE >= Vote.APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF); - DisplayModeDirector director = createDirectorFromFpsRange(60, 90); + Display.Mode[] modes = new Display.Mode[3]; + modes[0] = new Display.Mode( + /*modeId=*/60, /*width=*/1000, /*height=*/1000, 60); + modes[1] = new Display.Mode( + /*modeId=*/75, /*width=*/1000, /*height=*/1000, 75); + modes[2] = new Display.Mode( + /*modeId=*/90, /*width=*/1000, /*height=*/1000, 90); + + DisplayModeDirector director = createDirectorFromModeArray(modes, modes[1]); SparseArray votes = new SparseArray<>(); SparseArray> votesByDisplay = new SparseArray<>(); votesByDisplay.put(DISPLAY_ID, votes); - votes.put(PRIORITY_FLICKER, Vote.forRefreshRates(60, 60)); + votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH, Vote.forDisableRefreshRateSwitching()); + votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE, Vote.forRefreshRates(60, 60)); director.injectVotesByDisplay(votesByDisplay); DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(60); @@ -757,19 +923,10 @@ public class DisplayModeDirectorTest { votes.put(Vote.PRIORITY_APP_REQUEST_MAX_REFRESH_RATE, Vote.forRefreshRates(0, 75)); director.injectVotesByDisplay(votesByDisplay); desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); - // TODO(b/183226498): ideally the refresh rate range should be a single value since - // PRIORITY_FLICKER voted, however since PRIORITY_APP_REQUEST_MAX_REFRESH_RATE votes for - // range, PRIORITY_FLICKER is ignored. The same thing would happen with - // PRIORITY_LOW_POWER_MODE so this is not a regression. This will be fixed in a subsequent - // CL. - //assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(75); + assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(75); assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(75); - //assertThat(desiredSpecs.appRequestRefreshRateRange.min) - // .isWithin(FLOAT_TOLERANCE) - // .of(75); - assertThat(desiredSpecs.appRequestRefreshRateRange.max) - .isWithin(FLOAT_TOLERANCE) - .of(75); + assertThat(desiredSpecs.appRequestRefreshRateRange.min).isZero(); + assertThat(desiredSpecs.appRequestRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(75); } @Test @@ -778,10 +935,12 @@ public class DisplayModeDirectorTest { director.getAppRequestObserver().setAppRequest(DISPLAY_ID, 60, 0); Vote appRequestRefreshRate = - director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_REFRESH_RATE); + director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE); assertNotNull(appRequestRefreshRate); - assertThat(appRequestRefreshRate.refreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(60); - assertThat(appRequestRefreshRate.refreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(60); + assertThat(appRequestRefreshRate.refreshRateRange.min).isZero(); + assertThat(appRequestRefreshRate.refreshRateRange.max).isPositiveInfinity(); + assertThat(appRequestRefreshRate.disableRefreshRateSwitching).isFalse(); + assertThat(appRequestRefreshRate.baseModeRefreshRate).isWithin(FLOAT_TOLERANCE).of(60); assertThat(appRequestRefreshRate.height).isEqualTo(INVALID_SIZE); assertThat(appRequestRefreshRate.width).isEqualTo(INVALID_SIZE); @@ -789,6 +948,8 @@ public class DisplayModeDirectorTest { assertNotNull(appRequestSize); assertThat(appRequestSize.refreshRateRange.min).isZero(); assertThat(appRequestSize.refreshRateRange.max).isPositiveInfinity(); + assertThat(appRequestSize.disableRefreshRateSwitching).isFalse(); + assertThat(appRequestSize.baseModeRefreshRate).isZero(); assertThat(appRequestSize.height).isEqualTo(1000); assertThat(appRequestSize.width).isEqualTo(1000); @@ -799,10 +960,12 @@ public class DisplayModeDirectorTest { director.getAppRequestObserver().setAppRequest(DISPLAY_ID, 90, 0); appRequestRefreshRate = - director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_REFRESH_RATE); + director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE); assertNotNull(appRequestRefreshRate); - assertThat(appRequestRefreshRate.refreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(90); - assertThat(appRequestRefreshRate.refreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(90); + assertThat(appRequestRefreshRate.refreshRateRange.min).isZero(); + assertThat(appRequestRefreshRate.refreshRateRange.max).isPositiveInfinity(); + assertThat(appRequestRefreshRate.disableRefreshRateSwitching).isFalse(); + assertThat(appRequestRefreshRate.baseModeRefreshRate).isWithin(FLOAT_TOLERANCE).of(90); assertThat(appRequestRefreshRate.height).isEqualTo(INVALID_SIZE); assertThat(appRequestRefreshRate.width).isEqualTo(INVALID_SIZE); @@ -823,7 +986,7 @@ public class DisplayModeDirectorTest { DisplayModeDirector director = createDirectorFromFpsRange(60, 90); director.getAppRequestObserver().setAppRequest(DISPLAY_ID, -1, 90); Vote appRequestRefreshRate = - director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_REFRESH_RATE); + director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE); assertNull(appRequestRefreshRate); Vote appRequestSize = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE); @@ -839,7 +1002,7 @@ public class DisplayModeDirectorTest { director.getAppRequestObserver().setAppRequest(DISPLAY_ID, -1, 60); appRequestRefreshRate = - director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_REFRESH_RATE); + director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE); assertNull(appRequestRefreshRate); appRequestSize = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE); @@ -860,10 +1023,12 @@ public class DisplayModeDirectorTest { director.getAppRequestObserver().setAppRequest(DISPLAY_ID, 60, 90); Vote appRequestRefreshRate = - director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_REFRESH_RATE); + director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE); assertNotNull(appRequestRefreshRate); - assertThat(appRequestRefreshRate.refreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(60); - assertThat(appRequestRefreshRate.refreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(60); + assertThat(appRequestRefreshRate.refreshRateRange.min).isZero(); + assertThat(appRequestRefreshRate.refreshRateRange.max).isPositiveInfinity(); + assertThat(appRequestRefreshRate.disableRefreshRateSwitching).isFalse(); + assertThat(appRequestRefreshRate.baseModeRefreshRate).isWithin(FLOAT_TOLERANCE).of(60); assertThat(appRequestRefreshRate.height).isEqualTo(INVALID_SIZE); assertThat(appRequestRefreshRate.width).isEqualTo(INVALID_SIZE); @@ -884,6 +1049,170 @@ public class DisplayModeDirectorTest { assertThat(appRequestMaxRefreshRate.width).isEqualTo(INVALID_SIZE); } + @Test + public void testAppRequestsIsTheDefaultMode() { + Display.Mode[] modes = new Display.Mode[2]; + modes[0] = new Display.Mode( + /*modeId=*/1, /*width=*/1000, /*height=*/1000, 60); + modes[1] = new Display.Mode( + /*modeId=*/2, /*width=*/1000, /*height=*/1000, 90); + + DisplayModeDirector director = createDirectorFromModeArray(modes, modes[0]); + DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); + assertThat(desiredSpecs.baseModeId).isEqualTo(1); + assertThat(desiredSpecs.primaryRefreshRateRange.min).isAtMost(60); + assertThat(desiredSpecs.primaryRefreshRateRange.max).isAtLeast(90); + + SparseArray votes = new SparseArray<>(); + SparseArray> votesByDisplay = new SparseArray<>(); + votesByDisplay.put(DISPLAY_ID, votes); + Display.Mode appRequestedMode = modes[1]; + votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, + Vote.forBaseModeRefreshRate(appRequestedMode.getRefreshRate())); + votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(appRequestedMode.getPhysicalWidth(), + appRequestedMode.getPhysicalHeight())); + director.injectVotesByDisplay(votesByDisplay); + desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); + assertThat(desiredSpecs.baseModeId).isEqualTo(2); + assertThat(desiredSpecs.primaryRefreshRateRange.min).isAtMost(60); + assertThat(desiredSpecs.primaryRefreshRateRange.max).isAtLeast(90); + } + + @Test + public void testDisableRefreshRateSwitchingVote() { + DisplayModeDirector director = createDirectorFromFpsRange(50, 90); + SparseArray votes = new SparseArray<>(); + SparseArray> votesByDisplay = new SparseArray<>(); + votesByDisplay.put(DISPLAY_ID, votes); + votes.put(Vote.PRIORITY_USER_SETTING_MIN_REFRESH_RATE, + Vote.forRefreshRates(90, Float.POSITIVE_INFINITY)); + votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH, Vote.forDisableRefreshRateSwitching()); + votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRefreshRates(0, 60)); + director.injectVotesByDisplay(votesByDisplay); + DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); + assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(50); + assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(50); + assertThat(desiredSpecs.baseModeId).isEqualTo(50); + + votes.clear(); + votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE, + Vote.forRefreshRates(70, Float.POSITIVE_INFINITY)); + votes.put(Vote.PRIORITY_USER_SETTING_MIN_REFRESH_RATE, + Vote.forRefreshRates(80, Float.POSITIVE_INFINITY)); + votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH, Vote.forDisableRefreshRateSwitching()); + votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRefreshRates(0, 90)); + desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); + assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(80); + assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(80); + assertThat(desiredSpecs.baseModeId).isEqualTo(80); + + votes.clear(); + votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE, + Vote.forRefreshRates(90, Float.POSITIVE_INFINITY)); + votes.put(Vote.PRIORITY_USER_SETTING_MIN_REFRESH_RATE, + Vote.forRefreshRates(80, Float.POSITIVE_INFINITY)); + votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH, Vote.forDisableRefreshRateSwitching()); + votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRefreshRates(0, 90)); + desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); + assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(90); + assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(90); + assertThat(desiredSpecs.baseModeId).isEqualTo(90); + } + + @Test + public void testBaseModeIdInPrimaryRange() { + DisplayModeDirector director = createDirectorFromFpsRange(50, 90); + SparseArray votes = new SparseArray<>(); + SparseArray> votesByDisplay = new SparseArray<>(); + votesByDisplay.put(DISPLAY_ID, votes); + votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, + Vote.forBaseModeRefreshRate(70)); + votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRefreshRates(0, 60)); + director.injectVotesByDisplay(votesByDisplay); + DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); + assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(0); + assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(60); + assertThat(desiredSpecs.baseModeId).isEqualTo(50); + + votes.clear(); + votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, + Vote.forBaseModeRefreshRate(55)); + votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRefreshRates(0, 60)); + director.injectVotesByDisplay(votesByDisplay); + desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); + assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(0); + assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(60); + assertThat(desiredSpecs.baseModeId).isEqualTo(55); + + votes.clear(); + votes.put(Vote.PRIORITY_APP_REQUEST_MAX_REFRESH_RATE, Vote.forRefreshRates(0, 52)); + votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, + Vote.forBaseModeRefreshRate(55)); + votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRefreshRates(0, 60)); + director.injectVotesByDisplay(votesByDisplay); + desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); + assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(0); + assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(60); + assertThat(desiredSpecs.baseModeId).isEqualTo(55); + + votes.clear(); + votes.put(Vote.PRIORITY_APP_REQUEST_MAX_REFRESH_RATE, Vote.forRefreshRates(0, 58)); + votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, + Vote.forBaseModeRefreshRate(55)); + votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRefreshRates(0, 60)); + director.injectVotesByDisplay(votesByDisplay); + desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); + assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(0); + assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(58); + assertThat(desiredSpecs.baseModeId).isEqualTo(55); + } + + @Test + public void testStaleAppVote() { + Display.Mode[] modes = new Display.Mode[4]; + modes[0] = new Display.Mode( + /*modeId=*/1, /*width=*/1000, /*height=*/1000, 60); + modes[1] = new Display.Mode( + /*modeId=*/2, /*width=*/2000, /*height=*/2000, 60); + modes[2] = new Display.Mode( + /*modeId=*/3, /*width=*/1000, /*height=*/1000, 90); + modes[3] = new Display.Mode( + /*modeId=*/4, /*width=*/2000, /*height=*/2000, 90); + + DisplayModeDirector director = createDirectorFromModeArray(modes, modes[0]); + SparseArray votes = new SparseArray<>(); + SparseArray> votesByDisplay = new SparseArray<>(); + votesByDisplay.put(DISPLAY_ID, votes); + Display.Mode appRequestedMode = modes[3]; + votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, + Vote.forBaseModeRefreshRate(appRequestedMode.getRefreshRate())); + votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(appRequestedMode.getPhysicalWidth(), + appRequestedMode.getPhysicalHeight())); + director.injectVotesByDisplay(votesByDisplay); + DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); + assertThat(desiredSpecs.baseModeId).isEqualTo(4); + + // Change mode Id's to simulate that a hotplug has occurred. + Display.Mode[] newModes = new Display.Mode[4]; + newModes[0] = new Display.Mode( + /*modeId=*/5, /*width=*/1000, /*height=*/1000, 60); + newModes[1] = new Display.Mode( + /*modeId=*/6, /*width=*/2000, /*height=*/2000, 60); + newModes[2] = new Display.Mode( + /*modeId=*/7, /*width=*/1000, /*height=*/1000, 90); + newModes[3] = new Display.Mode( + /*modeId=*/8, /*width=*/2000, /*height=*/2000, 90); + + SparseArray supportedModesByDisplay = new SparseArray<>(); + supportedModesByDisplay.put(DISPLAY_ID, newModes); + director.injectSupportedModesByDisplay(supportedModesByDisplay); + SparseArray defaultModesByDisplay = new SparseArray<>(); + defaultModesByDisplay.put(DISPLAY_ID, newModes[0]); + director.injectDefaultModeByDisplay(defaultModesByDisplay); + desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); + assertThat(desiredSpecs.baseModeId).isEqualTo(8); + } + private void assertVoteForRefreshRate(Vote vote, float refreshRate) { assertThat(vote).isNotNull(); final DisplayModeDirector.RefreshRateRange expectedRange = diff --git a/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java index 325bca418d130..1ee646c57b14e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java @@ -77,12 +77,12 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase { assertNotNull("Window state is created", appWindow); assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET); - assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE); + assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE); appWindow.updateFrameRateSelectionPriorityIfNeeded(); // Priority doesn't change. assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET); - assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE); + assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE); // Call the function a few times. appWindow.updateFrameRateSelectionPriorityIfNeeded(); @@ -92,7 +92,7 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase { verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionPriority( any(SurfaceControl.class), anyInt()); verify(appWindow.getPendingTransaction(), never()).setFrameRate( - any(SurfaceControl.class), anyInt(), anyInt()); + any(SurfaceControl.class), anyInt(), anyInt(), anyInt()); } @Test @@ -101,16 +101,16 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase { assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET); assertEquals(appWindow.getDisplayContent().getDisplayPolicy().getRefreshRatePolicy() .getPreferredModeId(appWindow), 0); - assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE); + assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE); assertEquals(appWindow.getDisplayContent().getDisplayPolicy().getRefreshRatePolicy() .getPreferredRefreshRate(appWindow), 0, FLOAT_TOLERANCE); - assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE); + assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE); appWindow.updateFrameRateSelectionPriorityIfNeeded(); // Priority stays MAX_VALUE. assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET); - assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE); + assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE); verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionPriority( appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET); @@ -119,38 +119,38 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase { appWindow.updateFrameRateSelectionPriorityIfNeeded(); // Priority changes to 1. assertEquals(appWindow.mFrameRateSelectionPriority, 1); - assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE); + assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE); verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority( appWindow.getSurfaceControl(), 1); verify(appWindow.getPendingTransaction(), never()).setFrameRate( - any(SurfaceControl.class), anyInt(), anyInt()); + any(SurfaceControl.class), anyInt(), anyInt(), anyInt()); } @Test public void testApplicationInFocusWithModeId() { final WindowState appWindow = createWindow(null, TYPE_APPLICATION, "appWindow"); assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET); - assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE); + assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE); // Application is in focus. appWindow.mToken.mDisplayContent.mCurrentFocus = appWindow; appWindow.updateFrameRateSelectionPriorityIfNeeded(); // Priority changes. assertEquals(appWindow.mFrameRateSelectionPriority, 1); - assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE); + assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE); // Update the mode ID to a requested number. appWindow.mAttrs.preferredDisplayModeId = 1; appWindow.updateFrameRateSelectionPriorityIfNeeded(); // Priority changes. assertEquals(appWindow.mFrameRateSelectionPriority, 0); - assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE); + assertEquals(appWindow.mAppPreferredFrameRate, 60, FLOAT_TOLERANCE); // Remove the mode ID request. appWindow.mAttrs.preferredDisplayModeId = 0; appWindow.updateFrameRateSelectionPriorityIfNeeded(); // Priority changes. assertEquals(appWindow.mFrameRateSelectionPriority, 1); - assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE); + assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE); // Verify we called actions on Transactions correctly. verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionPriority( @@ -160,14 +160,14 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase { verify(appWindow.getPendingTransaction(), times(2)).setFrameRateSelectionPriority( appWindow.getSurfaceControl(), 1); verify(appWindow.getPendingTransaction(), never()).setFrameRate( - any(SurfaceControl.class), anyInt(), anyInt()); + any(SurfaceControl.class), anyInt(), anyInt(), anyInt()); } @Test public void testApplicationNotInFocusWithModeId() { final WindowState appWindow = createWindow(null, TYPE_APPLICATION, "appWindow"); assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET); - assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE); + assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE); final WindowState inFocusWindow = createWindow(null, TYPE_APPLICATION, "inFocus"); appWindow.mToken.mDisplayContent.mCurrentFocus = inFocusWindow; @@ -175,28 +175,28 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase { appWindow.updateFrameRateSelectionPriorityIfNeeded(); // The window is not in focus. assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET); - assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE); + assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE); // Update the mode ID to a requested number. appWindow.mAttrs.preferredDisplayModeId = 1; appWindow.updateFrameRateSelectionPriorityIfNeeded(); // Priority changes. assertEquals(appWindow.mFrameRateSelectionPriority, 2); - assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE); + assertEquals(appWindow.mAppPreferredFrameRate, 60, FLOAT_TOLERANCE); verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority( appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET); verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority( appWindow.getSurfaceControl(), 2); verify(appWindow.getPendingTransaction(), never()).setFrameRate( - any(SurfaceControl.class), anyInt(), anyInt()); + any(SurfaceControl.class), anyInt(), anyInt(), anyInt()); } @Test public void testApplicationNotInFocusWithoutModeId() { final WindowState appWindow = createWindow(null, TYPE_APPLICATION, "appWindow"); assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET); - assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE); + assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE); final WindowState inFocusWindow = createWindow(null, TYPE_APPLICATION, "inFocus"); appWindow.mToken.mDisplayContent.mCurrentFocus = inFocusWindow; @@ -204,19 +204,19 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase { appWindow.updateFrameRateSelectionPriorityIfNeeded(); // The window is not in focus. assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET); - assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE); + assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE); // Make sure that the mode ID is not set. appWindow.mAttrs.preferredDisplayModeId = 0; appWindow.updateFrameRateSelectionPriorityIfNeeded(); // Priority doesn't change. assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET); - assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE); + assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE); verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority( appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET); verify(appWindow.getPendingTransaction(), never()).setFrameRate( - any(SurfaceControl.class), anyInt(), anyInt()); + any(SurfaceControl.class), anyInt(), anyInt(), anyInt()); } @Test @@ -233,7 +233,7 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase { appWindow.updateFrameRateSelectionPriorityIfNeeded(); assertEquals(RefreshRatePolicy.LAYER_PRIORITY_UNSET, appWindow.mFrameRateSelectionPriority); - assertEquals(60, appWindow.mDenyListFrameRate, FLOAT_TOLERANCE); + assertEquals(60, appWindow.mAppPreferredFrameRate, FLOAT_TOLERANCE); // Call the function a few times. appWindow.updateFrameRateSelectionPriorityIfNeeded(); @@ -243,6 +243,7 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase { verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionPriority( any(SurfaceControl.class), anyInt()); verify(appWindow.getPendingTransaction(), times(1)).setFrameRate( - appWindow.getSurfaceControl(), 60, Surface.FRAME_RATE_COMPATIBILITY_EXACT); + appWindow.getSurfaceControl(), 60, + Surface.FRAME_RATE_COMPATIBILITY_EXACT, Surface.CHANGE_FRAME_RATE_ALWAYS); } } diff --git a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java index 619aee6eb919d..b89539cabbfe8 100644 --- a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java +++ b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java @@ -231,7 +231,7 @@ public class StubTransaction extends SurfaceControl.Transaction { @Override public SurfaceControl.Transaction setFrameRate(SurfaceControl sc, float frameRate, - int compatibility) { + int compatibility, int changeFrameRateStrategy) { return this; }