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..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,37 +935,52 @@ 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] 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. // 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 = 3; - public static final int PRIORITY_APP_REQUEST_SIZE = 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 // 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; + + // 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 = 7; + 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. @@ -953,7 +991,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. @@ -973,32 +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_REFRESH_RATE: - return "PRIORITY_APP_REQUEST_REFRESH_RATE"; + case PRIORITY_APP_REQUEST_MAX_REFRESH_RATE: + return "PRIORITY_APP_REQUEST_MAX_REFRESH_RATE"; + case PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE: + return "PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE"; case PRIORITY_APP_REQUEST_SIZE: return "PRIORITY_APP_REQUEST_SIZE"; case PRIORITY_USER_SETTING_PEAK_REFRESH_RATE: @@ -1018,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 + "}"; } } @@ -1182,14 +1254,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); } } @@ -1199,24 +1274,48 @@ 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); } + 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 +1337,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); + } } } @@ -1486,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); } } } @@ -1734,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. @@ -1744,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/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 8800a9e2d4792..91f75e57c092f 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; + } } } @@ -4230,6 +4238,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mLastHasContent, mTmpApplySurfaceChangesTransactionState.preferredRefreshRate, mTmpApplySurfaceChangesTransactionState.preferredModeId, + mTmpApplySurfaceChangesTransactionState.preferredMaxRefreshRate, mTmpApplySurfaceChangesTransactionState.preferMinimalPostProcessing, true /* inTraversal, must call performTraversalInTrans... below */); } @@ -4513,12 +4522,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; @@ -4527,6 +4537,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..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; @@ -95,18 +96,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; } @@ -145,6 +138,44 @@ 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; + } + + 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 app requests a certain refresh rate or mode, don't override it. + 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/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index b121bfc2aa7df..4eff18cd56220 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 */ @@ -5416,10 +5416,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 cda659f05adf0..fb2db2259c7e4 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,11 @@ 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.PRIORITY_FLICKER; +import static com.android.server.display.DisplayModeDirector.Vote.INVALID_SIZE; 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; @@ -137,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); @@ -218,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); @@ -230,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 @@ -275,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); @@ -297,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, @@ -374,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); @@ -396,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); @@ -497,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); @@ -592,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(); } @@ -635,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 @@ -724,6 +883,336 @@ 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); + + 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(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); + 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); + assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(75); + assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(75); + assertThat(desiredSpecs.appRequestRefreshRateRange.min).isZero(); + 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_BASE_MODE_REFRESH_RATE); + assertNotNull(appRequestRefreshRate); + 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); + + 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.disableRefreshRateSwitching).isFalse(); + assertThat(appRequestSize.baseModeRefreshRate).isZero(); + 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_BASE_MODE_REFRESH_RATE); + assertNotNull(appRequestRefreshRate); + 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); + + 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_BASE_MODE_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_BASE_MODE_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_BASE_MODE_REFRESH_RATE); + assertNotNull(appRequestRefreshRate); + 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); + + 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); + } + + @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/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); } } 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; }