From 305a57c5fc1d551b5942e5119d719d2b88d8da1c Mon Sep 17 00:00:00 2001 From: Steven Thomas Date: Fri, 17 Apr 2020 12:28:36 -0700 Subject: [PATCH] Split refresh rate range into two ranges To prevent low-priority refresh rate considerations from overriding the app frame rate as specified via the new setFrameRate() api, split the display refresh rate range into "primary" and "app request" ranges. The primary range includes the low priority considerations, while the app request range removes two lower priority considerations. In general, surface flinger will keep the display refresh rate within the primary range, but layers with frame rate settings via the setFrameRate() api may cause surface flinger to pick a refresh rate outside the primary range. Surface flinger will never choose a refresh rate outside the app request range specified by display manager. Bug: 148978562 Test: - Added a new unit test to DisplayModeDirectorTest to verify that display manager strips lower priority considerations when deciding the app request range. - Added a new unit test to RefreshRateConfigsTest to verify RefreshRateConfigs handles the primary vs app request range correctly. - Manual test: Confirmed that with the "force 90Hz refresh rate" option turned on, we don't switch to 60Hz when playing a 60Hz video. - Manual test: Confirmed that with the "force 90Hz refresh rate" option turned on, when an app calls setFrameRate(60), we stay at 60Hz. - Manual test: Modified a Pixel 4 XL to prefer 60Hz in low brightness, entered low brightness, and confirmed we don't touch boost to 90Hz. - Manual test: Confirmed that Maps stays at 60Hz on Pixel 4. - Manual test: Turned on verbose logs in RefreshRateConfigs.cpp, confirmed they look good. - Manual test: Inspected dumpsys output, confirmed the primary and app request refresh rate ranges are printed correctly. Change-Id: I2a7f5ded0831c90b9b1ac09d941963a63b824098 --- core/java/android/view/SurfaceControl.java | 48 +++-- core/jni/android_view_SurfaceControl.cpp | 62 ++++-- .../server/display/DisplayModeDirector.java | 183 ++++++++++++------ .../server/display/LocalDisplayAdapter.java | 17 +- .../display/LocalDisplayAdapterTest.java | 2 +- .../display/DisplayModeDirectorTest.java | 92 ++++++--- 6 files changed, 283 insertions(+), 121 deletions(-) diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index 76ed37c51bfe7..22fb6ace1547e 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -1464,8 +1464,23 @@ public final class SurfaceControl implements Parcelable { */ public static final class DesiredDisplayConfigSpecs { public int defaultConfig; - public float minRefreshRate; - public float maxRefreshRate; + /** + * The primary refresh rate range represents display manager's general guidance on the + * display configs surface flinger will consider when switching refresh rates. Unless + * surface flinger has a specific reason to do otherwise, it will stay within this range. + */ + public float primaryRefreshRateMin; + public float primaryRefreshRateMax; + /** + * The app request refresh rate range allows surface flinger to consider more display + * configs when switching refresh rates. Although surface flinger will generally stay within + * the primary range, specific considerations, such as layer frame rate settings specified + * via the setFrameRate() api, may cause surface flinger to go outside the primary + * range. Surface flinger never goes outside the app request range. The app request range + * will be greater than or equal to the primary refresh rate range, never smaller. + */ + public float appRequestRefreshRateMin; + public float appRequestRefreshRateMax; public DesiredDisplayConfigSpecs() {} @@ -1473,11 +1488,14 @@ public final class SurfaceControl implements Parcelable { copyFrom(other); } - public DesiredDisplayConfigSpecs( - int defaultConfig, float minRefreshRate, float maxRefreshRate) { + public DesiredDisplayConfigSpecs(int defaultConfig, float primaryRefreshRateMin, + float primaryRefreshRateMax, float appRequestRefreshRateMin, + float appRequestRefreshRateMax) { this.defaultConfig = defaultConfig; - this.minRefreshRate = minRefreshRate; - this.maxRefreshRate = maxRefreshRate; + this.primaryRefreshRateMin = primaryRefreshRateMin; + this.primaryRefreshRateMax = primaryRefreshRateMax; + this.appRequestRefreshRateMin = appRequestRefreshRateMin; + this.appRequestRefreshRateMax = appRequestRefreshRateMax; } @Override @@ -1490,8 +1508,10 @@ public final class SurfaceControl implements Parcelable { */ public boolean equals(DesiredDisplayConfigSpecs other) { return other != null && defaultConfig == other.defaultConfig - && minRefreshRate == other.minRefreshRate - && maxRefreshRate == other.maxRefreshRate; + && primaryRefreshRateMin == other.primaryRefreshRateMin + && primaryRefreshRateMax == other.primaryRefreshRateMax + && appRequestRefreshRateMin == other.appRequestRefreshRateMin + && appRequestRefreshRateMax == other.appRequestRefreshRateMax; } @Override @@ -1504,14 +1524,18 @@ public final class SurfaceControl implements Parcelable { */ public void copyFrom(DesiredDisplayConfigSpecs other) { defaultConfig = other.defaultConfig; - minRefreshRate = other.minRefreshRate; - maxRefreshRate = other.maxRefreshRate; + primaryRefreshRateMin = other.primaryRefreshRateMin; + primaryRefreshRateMax = other.primaryRefreshRateMax; + appRequestRefreshRateMin = other.appRequestRefreshRateMin; + appRequestRefreshRateMax = other.appRequestRefreshRateMax; } @Override public String toString() { - return String.format("defaultConfig=%d min=%.0f max=%.0f", defaultConfig, - minRefreshRate, maxRefreshRate); + return String.format("defaultConfig=%d primaryRefreshRateRange=[%.0f %.0f]" + + " appRequestRefreshRateRange=[%.0f %.0f]", + defaultConfig, primaryRefreshRateMin, primaryRefreshRateMax, + appRequestRefreshRateMin, appRequestRefreshRateMax); } } diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index 58697875b2c32..36b4b6aad4b46 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -174,8 +174,10 @@ static struct { jclass clazz; jmethodID ctor; jfieldID defaultConfig; - jfieldID minRefreshRate; - jfieldID maxRefreshRate; + jfieldID primaryRefreshRateMin; + jfieldID primaryRefreshRateMax; + jfieldID appRequestRefreshRateMin; + jfieldID appRequestRefreshRateMax; } gDesiredDisplayConfigSpecsClassInfo; class JNamedColorSpace { @@ -919,13 +921,24 @@ static jboolean nativeSetDesiredDisplayConfigSpecs(JNIEnv* env, jclass clazz, jo jint defaultConfig = env->GetIntField(desiredDisplayConfigSpecs, gDesiredDisplayConfigSpecsClassInfo.defaultConfig); - jfloat minRefreshRate = env->GetFloatField(desiredDisplayConfigSpecs, - gDesiredDisplayConfigSpecsClassInfo.minRefreshRate); - jfloat maxRefreshRate = env->GetFloatField(desiredDisplayConfigSpecs, - gDesiredDisplayConfigSpecsClassInfo.maxRefreshRate); + jfloat primaryRefreshRateMin = + env->GetFloatField(desiredDisplayConfigSpecs, + gDesiredDisplayConfigSpecsClassInfo.primaryRefreshRateMin); + jfloat primaryRefreshRateMax = + env->GetFloatField(desiredDisplayConfigSpecs, + gDesiredDisplayConfigSpecsClassInfo.primaryRefreshRateMax); + jfloat appRequestRefreshRateMin = + env->GetFloatField(desiredDisplayConfigSpecs, + gDesiredDisplayConfigSpecsClassInfo.appRequestRefreshRateMin); + jfloat appRequestRefreshRateMax = + env->GetFloatField(desiredDisplayConfigSpecs, + gDesiredDisplayConfigSpecsClassInfo.appRequestRefreshRateMax); - size_t result = SurfaceComposerClient::setDesiredDisplayConfigSpecs( - token, defaultConfig, minRefreshRate, maxRefreshRate); + size_t result = SurfaceComposerClient::setDesiredDisplayConfigSpecs(token, defaultConfig, + primaryRefreshRateMin, + primaryRefreshRateMax, + appRequestRefreshRateMin, + appRequestRefreshRateMax); return result == NO_ERROR ? JNI_TRUE : JNI_FALSE; } @@ -934,16 +947,23 @@ static jobject nativeGetDesiredDisplayConfigSpecs(JNIEnv* env, jclass clazz, job if (token == nullptr) return nullptr; int32_t defaultConfig; - float minRefreshRate; - float maxRefreshRate; - if (SurfaceComposerClient::getDesiredDisplayConfigSpecs(token, &defaultConfig, &minRefreshRate, - &maxRefreshRate) != NO_ERROR) { + float primaryRefreshRateMin; + float primaryRefreshRateMax; + float appRequestRefreshRateMin; + float appRequestRefreshRateMax; + if (SurfaceComposerClient::getDesiredDisplayConfigSpecs(token, &defaultConfig, + &primaryRefreshRateMin, + &primaryRefreshRateMax, + &appRequestRefreshRateMin, + &appRequestRefreshRateMax) != + NO_ERROR) { return nullptr; } return env->NewObject(gDesiredDisplayConfigSpecsClassInfo.clazz, - gDesiredDisplayConfigSpecsClassInfo.ctor, defaultConfig, minRefreshRate, - maxRefreshRate); + gDesiredDisplayConfigSpecsClassInfo.ctor, defaultConfig, + primaryRefreshRateMin, primaryRefreshRateMax, appRequestRefreshRateMin, + appRequestRefreshRateMax); } static jint nativeGetActiveConfig(JNIEnv* env, jclass clazz, jobject tokenObj) { @@ -1757,13 +1777,17 @@ int register_android_view_SurfaceControl(JNIEnv* env) gDesiredDisplayConfigSpecsClassInfo.clazz = MakeGlobalRefOrDie(env, desiredDisplayConfigSpecsClazz); gDesiredDisplayConfigSpecsClassInfo.ctor = - GetMethodIDOrDie(env, gDesiredDisplayConfigSpecsClassInfo.clazz, "", "(IFF)V"); + GetMethodIDOrDie(env, gDesiredDisplayConfigSpecsClassInfo.clazz, "", "(IFFFF)V"); gDesiredDisplayConfigSpecsClassInfo.defaultConfig = GetFieldIDOrDie(env, desiredDisplayConfigSpecsClazz, "defaultConfig", "I"); - gDesiredDisplayConfigSpecsClassInfo.minRefreshRate = - GetFieldIDOrDie(env, desiredDisplayConfigSpecsClazz, "minRefreshRate", "F"); - gDesiredDisplayConfigSpecsClassInfo.maxRefreshRate = - GetFieldIDOrDie(env, desiredDisplayConfigSpecsClazz, "maxRefreshRate", "F"); + gDesiredDisplayConfigSpecsClassInfo.primaryRefreshRateMin = + GetFieldIDOrDie(env, desiredDisplayConfigSpecsClazz, "primaryRefreshRateMin", "F"); + gDesiredDisplayConfigSpecsClassInfo.primaryRefreshRateMax = + GetFieldIDOrDie(env, desiredDisplayConfigSpecsClazz, "primaryRefreshRateMax", "F"); + gDesiredDisplayConfigSpecsClassInfo.appRequestRefreshRateMin = + GetFieldIDOrDie(env, desiredDisplayConfigSpecsClazz, "appRequestRefreshRateMin", "F"); + gDesiredDisplayConfigSpecsClassInfo.appRequestRefreshRateMax = + GetFieldIDOrDie(env, desiredDisplayConfigSpecsClazz, "appRequestRefreshRateMax", "F"); return err; } diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java index 612fd39634eaa..c54ebf87e5586 100644 --- a/services/core/java/com/android/server/display/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/DisplayModeDirector.java @@ -152,6 +152,47 @@ public class DisplayModeDirector { return votes; } + private static final class VoteSummary { + public float minRefreshRate; + public float maxRefreshRate; + public int width; + public int height; + + VoteSummary() { + reset(); + } + + public void reset() { + minRefreshRate = 0f; + maxRefreshRate = Float.POSITIVE_INFINITY; + width = Vote.INVALID_SIZE; + height = Vote.INVALID_SIZE; + } + } + + // VoteSummary is returned as an output param to cut down a bit on the number of temporary + // objects. + private void summarizeVotes( + SparseArray votes, int lowestConsideredPriority, /*out*/ VoteSummary summary) { + summary.reset(); + for (int priority = Vote.MAX_PRIORITY; priority >= lowestConsideredPriority; priority--) { + Vote vote = votes.get(priority); + if (vote == null) { + continue; + } + // 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). + 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; + } + } + } + /** * Calculates the refresh rate ranges and display modes that the system is allowed to freely * switch between based on global and display-specific constraints. @@ -174,52 +215,31 @@ public class DisplayModeDirector { } int[] availableModes = new int[]{defaultMode.getModeId()}; - float minRefreshRate = 0f; - float maxRefreshRate = Float.POSITIVE_INFINITY; + VoteSummary primarySummary = new VoteSummary(); int lowestConsideredPriority = Vote.MIN_PRIORITY; while (lowestConsideredPriority <= Vote.MAX_PRIORITY) { - minRefreshRate = 0f; - maxRefreshRate = Float.POSITIVE_INFINITY; - int height = Vote.INVALID_SIZE; - int width = Vote.INVALID_SIZE; - - for (int priority = Vote.MAX_PRIORITY; - priority >= lowestConsideredPriority; priority--) { - Vote vote = votes.get(priority); - if (vote == null) { - continue; - } - // For refresh rates, just use the tightest bounds of all the votes - minRefreshRate = Math.max(minRefreshRate, vote.refreshRateRange.min); - maxRefreshRate = Math.min(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). - if (height == Vote.INVALID_SIZE && width == Vote.INVALID_SIZE - && vote.height > 0 && vote.width > 0) { - width = vote.width; - height = vote.height; - } - } + summarizeVotes(votes, lowestConsideredPriority, primarySummary); // If we don't have anything specifying the width / height of the display, just use // the default width and height. We don't want these switching out from underneath // us since it's a pretty disruptive behavior. - if (height == Vote.INVALID_SIZE || width == Vote.INVALID_SIZE) { - width = defaultMode.getPhysicalWidth(); - height = defaultMode.getPhysicalHeight(); + if (primarySummary.height == Vote.INVALID_SIZE + || primarySummary.width == Vote.INVALID_SIZE) { + primarySummary.width = defaultMode.getPhysicalWidth(); + primarySummary.height = defaultMode.getPhysicalHeight(); } - availableModes = filterModes(modes, width, height, minRefreshRate, maxRefreshRate); + availableModes = filterModes(modes, primarySummary); if (availableModes.length > 0) { if (DEBUG) { Slog.w(TAG, "Found available modes=" + Arrays.toString(availableModes) + " with lowest priority considered " + Vote.priorityToString(lowestConsideredPriority) + " and constraints: " - + "width=" + width - + ", height=" + height - + ", minRefreshRate=" + minRefreshRate - + ", maxRefreshRate=" + maxRefreshRate); + + "width=" + primarySummary.width + + ", height=" + primarySummary.height + + ", minRefreshRate=" + primarySummary.minRefreshRate + + ", maxRefreshRate=" + primarySummary.maxRefreshRate); } break; } @@ -228,10 +248,10 @@ public class DisplayModeDirector { Slog.w(TAG, "Couldn't find available modes with lowest priority set to " + Vote.priorityToString(lowestConsideredPriority) + " and with the following constraints: " - + "width=" + width - + ", height=" + height - + ", minRefreshRate=" + minRefreshRate - + ", maxRefreshRate=" + maxRefreshRate); + + "width=" + primarySummary.width + + ", height=" + primarySummary.height + + ", minRefreshRate=" + primarySummary.minRefreshRate + + ", maxRefreshRate=" + primarySummary.maxRefreshRate); } // If we haven't found anything with the current set of votes, drop the @@ -239,6 +259,20 @@ public class DisplayModeDirector { lowestConsideredPriority++; } + VoteSummary appRequestSummary = new VoteSummary(); + summarizeVotes( + votes, Vote.APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF, appRequestSummary); + appRequestSummary.minRefreshRate = + Math.min(appRequestSummary.minRefreshRate, primarySummary.minRefreshRate); + appRequestSummary.maxRefreshRate = + Math.max(appRequestSummary.maxRefreshRate, primarySummary.maxRefreshRate); + if (DEBUG) { + Slog.i(TAG, + String.format("App request range: [%.0f %.0f]", + appRequestSummary.minRefreshRate, + appRequestSummary.maxRefreshRate)); + } + int baseModeId = defaultMode.getModeId(); if (availableModes.length > 0) { baseModeId = availableModes[0]; @@ -246,20 +280,23 @@ public class DisplayModeDirector { // filterModes function is going to filter the modes based on the voting system. If // the application requests a given mode with preferredModeId function, it will be // stored as baseModeId. - return new DesiredDisplayModeSpecs( - baseModeId, new RefreshRateRange(minRefreshRate, maxRefreshRate)); + return new DesiredDisplayModeSpecs(baseModeId, + new RefreshRateRange( + primarySummary.minRefreshRate, primarySummary.maxRefreshRate), + new RefreshRateRange( + appRequestSummary.minRefreshRate, appRequestSummary.maxRefreshRate)); } } - private int[] filterModes(Display.Mode[] supportedModes, - int width, int height, float minRefreshRate, float maxRefreshRate) { + private int[] filterModes(Display.Mode[] supportedModes, VoteSummary summary) { ArrayList availableModes = new ArrayList<>(); for (Display.Mode mode : supportedModes) { - if (mode.getPhysicalWidth() != width || mode.getPhysicalHeight() != height) { + if (mode.getPhysicalWidth() != summary.width + || mode.getPhysicalHeight() != summary.height) { if (DEBUG) { Slog.w(TAG, "Discarding mode " + mode.getModeId() + ", wrong size" - + ": desiredWidth=" + width - + ": desiredHeight=" + height + + ": desiredWidth=" + summary.width + + ": desiredHeight=" + summary.height + ": actualWidth=" + mode.getPhysicalWidth() + ": actualHeight=" + mode.getPhysicalHeight()); } @@ -269,13 +306,13 @@ public class DisplayModeDirector { // Some refresh rates are calculated based on frame timings, so they aren't *exactly* // equal to expected refresh rate. Given that, we apply a bit of tolerance to this // comparison. - if (refreshRate < (minRefreshRate - FLOAT_TOLERANCE) - || refreshRate > (maxRefreshRate + FLOAT_TOLERANCE)) { + if (refreshRate < (summary.minRefreshRate - FLOAT_TOLERANCE) + || refreshRate > (summary.maxRefreshRate + FLOAT_TOLERANCE)) { if (DEBUG) { Slog.w(TAG, "Discarding mode " + mode.getModeId() + ", outside refresh rate bounds" - + ": minRefreshRate=" + minRefreshRate - + ", maxRefreshRate=" + maxRefreshRate + + ": minRefreshRate=" + summary.minRefreshRate + + ", maxRefreshRate=" + summary.maxRefreshRate + ", modeRefreshRate=" + refreshRate); } continue; @@ -535,7 +572,7 @@ public class DisplayModeDirector { /** * Information about the desired display mode to be set by the system. Includes the base - * mode ID and refresh rate range. + * mode ID and the primary and app request refresh rate ranges. * * We have this class in addition to SurfaceControl.DesiredDisplayConfigSpecs to make clear the * distinction between the config ID / physical index that @@ -548,17 +585,28 @@ public class DisplayModeDirector { */ public int baseModeId; /** - * The refresh rate range. + * The primary refresh rate range. */ - public final RefreshRateRange refreshRateRange; + public final RefreshRateRange primaryRefreshRateRange; + /** + * The app request refresh rate range. Lower priority considerations won't be included in + * this range, allowing surface flinger to consider additional refresh rates for apps that + * call setFrameRate(). This range will be greater than or equal to the primary refresh rate + * range, never smaller. + */ + public final RefreshRateRange appRequestRefreshRateRange; public DesiredDisplayModeSpecs() { - refreshRateRange = new RefreshRateRange(); + primaryRefreshRateRange = new RefreshRateRange(); + appRequestRefreshRateRange = new RefreshRateRange(); } - public DesiredDisplayModeSpecs(int baseModeId, @NonNull RefreshRateRange refreshRateRange) { + public DesiredDisplayModeSpecs(int baseModeId, + @NonNull RefreshRateRange primaryRefreshRateRange, + @NonNull RefreshRateRange appRequestRefreshRateRange) { this.baseModeId = baseModeId; - this.refreshRateRange = refreshRateRange; + this.primaryRefreshRateRange = primaryRefreshRateRange; + this.appRequestRefreshRateRange = appRequestRefreshRateRange; } /** @@ -566,8 +614,10 @@ public class DisplayModeDirector { */ @Override public String toString() { - return String.format("baseModeId=%d min=%.0f max=%.0f", baseModeId, - refreshRateRange.min, refreshRateRange.max); + return String.format("baseModeId=%d primaryRefreshRateRange=[%.0f %.0f]" + + " appRequestRefreshRateRange=[%.0f %.0f]", + baseModeId, primaryRefreshRateRange.min, primaryRefreshRateRange.max, + appRequestRefreshRateRange.min, appRequestRefreshRateRange.max); } /** * Checks whether the two objects have the same values. @@ -587,7 +637,11 @@ public class DisplayModeDirector { if (baseModeId != desiredDisplayModeSpecs.baseModeId) { return false; } - if (!refreshRateRange.equals(desiredDisplayModeSpecs.refreshRateRange)) { + if (!primaryRefreshRateRange.equals(desiredDisplayModeSpecs.primaryRefreshRateRange)) { + return false; + } + if (!appRequestRefreshRateRange.equals( + desiredDisplayModeSpecs.appRequestRefreshRateRange)) { return false; } return true; @@ -595,7 +649,7 @@ public class DisplayModeDirector { @Override public int hashCode() { - return Objects.hash(baseModeId, refreshRateRange); + return Objects.hash(baseModeId, primaryRefreshRateRange, appRequestRefreshRateRange); } /** @@ -603,8 +657,10 @@ public class DisplayModeDirector { */ public void copyFrom(DesiredDisplayModeSpecs other) { baseModeId = other.baseModeId; - refreshRateRange.min = other.refreshRateRange.min; - refreshRateRange.max = other.refreshRateRange.max; + primaryRefreshRateRange.min = other.primaryRefreshRateRange.min; + primaryRefreshRateRange.max = other.primaryRefreshRateRange.max; + appRequestRefreshRateRange.min = other.appRequestRefreshRateRange.min; + appRequestRefreshRateRange.max = other.appRequestRefreshRateRange.max; } } @@ -637,12 +693,17 @@ 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 = 5; - // Whenever a new priority is added, remember to update MIN_PRIORITY and/or MAX_PRIORITY as - // appropriate, as well as priorityToString. + // 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. public static final int MIN_PRIORITY = PRIORITY_LOW_BRIGHTNESS; public static final int MAX_PRIORITY = PRIORITY_LOW_POWER_MODE; + // 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; + /** * A value signifying an invalid width or height in a vote. */ diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index 6132467103a9c..4f5a02ad22fac 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -310,9 +310,14 @@ final class LocalDisplayAdapter extends DisplayAdapter { // list of available modes will take care of updating display config specs. if (activeBaseMode != NO_DISPLAY_MODE_ID) { if (mDisplayModeSpecs.baseModeId != activeBaseMode - || mDisplayModeSpecs.refreshRateRange.min != configSpecs.minRefreshRate - || mDisplayModeSpecs.refreshRateRange.max - != configSpecs.maxRefreshRate) { + || mDisplayModeSpecs.primaryRefreshRateRange.min + != configSpecs.primaryRefreshRateMin + || mDisplayModeSpecs.primaryRefreshRateRange.max + != configSpecs.primaryRefreshRateMax + || mDisplayModeSpecs.appRequestRefreshRateRange.min + != configSpecs.appRequestRefreshRateMin + || mDisplayModeSpecs.appRequestRefreshRateRange.max + != configSpecs.appRequestRefreshRateMax) { mDisplayModeSpecsInvalid = true; sendTraversalRequestLocked(); } @@ -799,8 +804,10 @@ final class LocalDisplayAdapter extends DisplayAdapter { LocalDisplayDevice::setDesiredDisplayModeSpecsAsync, this, getDisplayTokenLocked(), new SurfaceControl.DesiredDisplayConfigSpecs(baseConfigId, - mDisplayModeSpecs.refreshRateRange.min, - mDisplayModeSpecs.refreshRateRange.max))); + mDisplayModeSpecs.primaryRefreshRateRange.min, + mDisplayModeSpecs.primaryRefreshRateRange.max, + mDisplayModeSpecs.appRequestRefreshRateRange.min, + mDisplayModeSpecs.appRequestRefreshRateRange.max))); } } diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java index 6d15302193726..8abddc82a90bf 100644 --- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java @@ -308,7 +308,7 @@ public class LocalDisplayAdapterTest { doReturn(0).when(() -> SurfaceControl.getActiveColorMode(display.token)); doReturn(new int[] { 0 }).when( () -> SurfaceControl.getDisplayColorModes(display.token)); - doReturn(new SurfaceControl.DesiredDisplayConfigSpecs(0, 60.f, 60.f)) + doReturn(new SurfaceControl.DesiredDisplayConfigSpecs(0, 60.f, 60.f, 60.f, 60.f)) .when(() -> SurfaceControl.getDesiredDisplayConfigSpecs(display.token)); } 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 08bd1ee3c3890..8137c36ea350c 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java +++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java @@ -16,7 +16,6 @@ package com.android.server.display; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import android.content.Context; @@ -30,7 +29,6 @@ import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.server.display.DisplayModeDirector.DesiredDisplayModeSpecs; -import com.android.server.display.DisplayModeDirector.RefreshRateRange; import com.android.server.display.DisplayModeDirector.Vote; import com.google.common.truth.Truth; @@ -79,10 +77,12 @@ public class DisplayModeDirectorTest { int displayId = 0; // With no votes present, DisplayModeDirector should allow any refresh rate. - assertEquals(new DesiredDisplayModeSpecs(/*baseModeId=*/60, - new RefreshRateRange(0f, Float.POSITIVE_INFINITY)), + DesiredDisplayModeSpecs modeSpecs = createDisplayModeDirectorWithDisplayFpsRange(60, 90).getDesiredDisplayModeSpecs( - displayId)); + displayId); + Truth.assertThat(modeSpecs.baseModeId).isEqualTo(60); + Truth.assertThat(modeSpecs.primaryRefreshRateRange.min).isEqualTo(0f); + Truth.assertThat(modeSpecs.primaryRefreshRateRange.max).isEqualTo(Float.POSITIVE_INFINITY); int numPriorities = DisplayModeDirector.Vote.MAX_PRIORITY - DisplayModeDirector.Vote.MIN_PRIORITY + 1; @@ -101,10 +101,12 @@ public class DisplayModeDirectorTest { int priority = Vote.MIN_PRIORITY + i; votes.put(priority, Vote.forRefreshRates(minFps + i, maxFps - i)); director.injectVotesByDisplay(votesByDisplay); - assertEquals(new DesiredDisplayModeSpecs( - /*baseModeId=*/minFps + i, - new RefreshRateRange(minFps + i, maxFps - i)), - director.getDesiredDisplayModeSpecs(displayId)); + modeSpecs = director.getDesiredDisplayModeSpecs(displayId); + Truth.assertThat(modeSpecs.baseModeId).isEqualTo(minFps + i); + Truth.assertThat(modeSpecs.primaryRefreshRateRange.min) + .isEqualTo((float) (minFps + i)); + Truth.assertThat(modeSpecs.primaryRefreshRateRange.max) + .isEqualTo((float) (maxFps - i)); } } @@ -119,9 +121,10 @@ public class DisplayModeDirectorTest { votes.put(Vote.MAX_PRIORITY, Vote.forRefreshRates(65, 85)); votes.put(Vote.MIN_PRIORITY, Vote.forRefreshRates(70, 80)); director.injectVotesByDisplay(votesByDisplay); - assertEquals(new DesiredDisplayModeSpecs(/*baseModeId=*/70, - new RefreshRateRange(70, 80)), - director.getDesiredDisplayModeSpecs(displayId)); + modeSpecs = director.getDesiredDisplayModeSpecs(displayId); + Truth.assertThat(modeSpecs.baseModeId).isEqualTo(70); + Truth.assertThat(modeSpecs.primaryRefreshRateRange.min).isEqualTo(70f); + Truth.assertThat(modeSpecs.primaryRefreshRateRange.max).isEqualTo(80f); } } @@ -140,8 +143,8 @@ public class DisplayModeDirectorTest { director.injectVotesByDisplay(votesByDisplay); DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(displayId); - Truth.assertThat(desiredSpecs.refreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(60); - Truth.assertThat(desiredSpecs.refreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(60); + Truth.assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(60); + Truth.assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(60); Truth.assertThat(desiredSpecs.baseModeId).isEqualTo(60); } @@ -159,34 +162,77 @@ public class DisplayModeDirectorTest { votes.put(Vote.PRIORITY_LOW_BRIGHTNESS, Vote.forRefreshRates(60, 60)); director.injectVotesByDisplay(votesByDisplay); DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(displayId); - Truth.assertThat(desiredSpecs.refreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(60); - Truth.assertThat(desiredSpecs.refreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(60); + Truth.assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(60); + Truth.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(Vote.PRIORITY_LOW_BRIGHTNESS, Vote.forRefreshRates(90, 90)); director.injectVotesByDisplay(votesByDisplay); desiredSpecs = director.getDesiredDisplayModeSpecs(displayId); - Truth.assertThat(desiredSpecs.refreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(90); - Truth.assertThat(desiredSpecs.refreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(90); - + Truth.assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(90); + Truth.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(Vote.PRIORITY_LOW_BRIGHTNESS, Vote.forRefreshRates(60, 60)); director.injectVotesByDisplay(votesByDisplay); desiredSpecs = director.getDesiredDisplayModeSpecs(displayId); - Truth.assertThat(desiredSpecs.refreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(90); - Truth.assertThat(desiredSpecs.refreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(90); + Truth.assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(90); + Truth.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(Vote.PRIORITY_LOW_BRIGHTNESS, Vote.forRefreshRates(90, 90)); director.injectVotesByDisplay(votesByDisplay); desiredSpecs = director.getDesiredDisplayModeSpecs(displayId); - Truth.assertThat(desiredSpecs.refreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(60); - Truth.assertThat(desiredSpecs.refreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(60); + Truth.assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(60); + Truth.assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(60); + } + @Test + public void testAppRequestRefreshRateRange() { + // Confirm that the app request range doesn't include low brightness or min refresh rate + // settings, but does include everything else. + assertTrue( + Vote.PRIORITY_LOW_BRIGHTNESS < 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 + >= Vote.APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF); + int displayId = 0; + DisplayModeDirector director = createDisplayModeDirectorWithDisplayFpsRange(60, 90); + SparseArray votes = new SparseArray<>(); + SparseArray> votesByDisplay = new SparseArray<>(); + votesByDisplay.put(displayId, votes); + votes.put(Vote.PRIORITY_LOW_BRIGHTNESS, Vote.forRefreshRates(60, 60)); + director.injectVotesByDisplay(votesByDisplay); + DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(displayId); + Truth.assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(60); + Truth.assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(60); + Truth.assertThat(desiredSpecs.appRequestRefreshRateRange.min).isAtMost(60f); + Truth.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(displayId); + Truth.assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(90); + Truth.assertThat(desiredSpecs.primaryRefreshRateRange.max).isAtLeast(90f); + Truth.assertThat(desiredSpecs.appRequestRefreshRateRange.min).isAtMost(60f); + Truth.assertThat(desiredSpecs.appRequestRefreshRateRange.max).isAtLeast(90f); + + votes.put(Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, Vote.forRefreshRates(75, 75)); + director.injectVotesByDisplay(votesByDisplay); + desiredSpecs = director.getDesiredDisplayModeSpecs(displayId); + Truth.assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(75); + Truth.assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(75); + Truth.assertThat(desiredSpecs.appRequestRefreshRateRange.min) + .isWithin(FLOAT_TOLERANCE) + .of(75); + Truth.assertThat(desiredSpecs.appRequestRefreshRateRange.max) + .isWithin(FLOAT_TOLERANCE) + .of(75); } }