diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index 9bee077ad77e9ee4bf00fd79bf5c659acb55b4b4..07dd882807af0f20e134b182612f00f35c60f890 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -2094,7 +2094,8 @@ public final class Display { private final int mModeId; private final int mWidth; private final int mHeight; - private final float mRefreshRate; + private final float mPeakRefreshRate; + private final float mVsyncRate; @NonNull private final float[] mAlternativeRefreshRates; @NonNull @@ -2106,7 +2107,15 @@ public final class Display { */ @TestApi public Mode(int width, int height, float refreshRate) { - this(INVALID_MODE_ID, width, height, refreshRate, new float[0], new int[0]); + this(INVALID_MODE_ID, width, height, refreshRate, refreshRate, new float[0], + new int[0]); + } + + /** + * @hide + */ + public Mode(int width, int height, float refreshRate, float vsyncRate) { + this(INVALID_MODE_ID, width, height, refreshRate, vsyncRate, new float[0], new int[0]); } /** @@ -2114,18 +2123,29 @@ public final class Display { */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public Mode(int modeId, int width, int height, float refreshRate) { - this(modeId, width, height, refreshRate, new float[0], new int[0]); + this(modeId, width, height, refreshRate, refreshRate, new float[0], new int[0]); } /** * @hide */ public Mode(int modeId, int width, int height, float refreshRate, + float[] alternativeRefreshRates, + @HdrCapabilities.HdrType int[] supportedHdrTypes) { + this(modeId, width, height, refreshRate, refreshRate, alternativeRefreshRates, + supportedHdrTypes); + } + + /** + * @hide + */ + public Mode(int modeId, int width, int height, float refreshRate, float vsyncRate, float[] alternativeRefreshRates, @HdrCapabilities.HdrType int[] supportedHdrTypes) { mModeId = modeId; mWidth = width; mHeight = height; - mRefreshRate = refreshRate; + mPeakRefreshRate = refreshRate; + mVsyncRate = vsyncRate; mAlternativeRefreshRates = Arrays.copyOf(alternativeRefreshRates, alternativeRefreshRates.length); Arrays.sort(mAlternativeRefreshRates); @@ -2176,7 +2196,17 @@ public final class Display { * Returns the refresh rate in frames per second. */ public float getRefreshRate() { - return mRefreshRate; + return mPeakRefreshRate; + } + + /** + * Returns the vsync rate in frames per second. + * The physical vsync rate may be higher than the refresh rate, as the refresh rate may be + * constrained by the system. + * @hide + */ + public float getVsyncRate() { + return mVsyncRate; } /** @@ -2222,7 +2252,7 @@ public final class Display { public boolean matches(int width, int height, float refreshRate) { return mWidth == width && mHeight == height && - Float.floatToIntBits(mRefreshRate) == Float.floatToIntBits(refreshRate); + Float.floatToIntBits(mPeakRefreshRate) == Float.floatToIntBits(refreshRate); } /** @@ -2235,9 +2265,9 @@ public final class Display { * * @hide */ - public boolean matchesIfValid(int width, int height, float refreshRate) { + public boolean matchesIfValid(int width, int height, float peakRefreshRate) { if (!isWidthValid(width) && !isHeightValid(height) - && !isRefreshRateValid(refreshRate)) { + && !isRefreshRateValid(peakRefreshRate)) { return false; } if (isWidthValid(width) != isHeightValid(height)) { @@ -2245,8 +2275,9 @@ public final class Display { } return (!isWidthValid(width) || mWidth == width) && (!isHeightValid(height) || mHeight == height) - && (!isRefreshRateValid(refreshRate) - || Float.floatToIntBits(mRefreshRate) == Float.floatToIntBits(refreshRate)); + && (!isRefreshRateValid(peakRefreshRate) + || Float.floatToIntBits(mPeakRefreshRate) + == Float.floatToIntBits(peakRefreshRate)); } /** @@ -2265,7 +2296,7 @@ public final class Display { * @hide */ public boolean isRefreshRateSet() { - return mRefreshRate != INVALID_DISPLAY_REFRESH_RATE; + return mPeakRefreshRate != INVALID_DISPLAY_REFRESH_RATE; } /** @@ -2286,7 +2317,8 @@ public final class Display { return false; } Mode that = (Mode) other; - return mModeId == that.mModeId && matches(that.mWidth, that.mHeight, that.mRefreshRate) + return mModeId == that.mModeId + && matches(that.mWidth, that.mHeight, that.mPeakRefreshRate) && Arrays.equals(mAlternativeRefreshRates, that.mAlternativeRefreshRates) && Arrays.equals(mSupportedHdrTypes, that.mSupportedHdrTypes); } @@ -2297,7 +2329,8 @@ public final class Display { hash = hash * 17 + mModeId; hash = hash * 17 + mWidth; hash = hash * 17 + mHeight; - hash = hash * 17 + Float.floatToIntBits(mRefreshRate); + hash = hash * 17 + Float.floatToIntBits(mPeakRefreshRate); + hash = hash * 17 + Float.floatToIntBits(mVsyncRate); hash = hash * 17 + Arrays.hashCode(mAlternativeRefreshRates); hash = hash * 17 + Arrays.hashCode(mSupportedHdrTypes); return hash; @@ -2309,7 +2342,8 @@ public final class Display { .append("id=").append(mModeId) .append(", width=").append(mWidth) .append(", height=").append(mHeight) - .append(", fps=").append(mRefreshRate) + .append(", fps=").append(mPeakRefreshRate) + .append(", vsync=").append(mVsyncRate) .append(", alternativeRefreshRates=") .append(Arrays.toString(mAlternativeRefreshRates)) .append(", supportedHdrTypes=") @@ -2324,8 +2358,8 @@ public final class Display { } private Mode(Parcel in) { - this(in.readInt(), in.readInt(), in.readInt(), in.readFloat(), in.createFloatArray(), - in.createIntArray()); + this(in.readInt(), in.readInt(), in.readInt(), in.readFloat(), in.readFloat(), + in.createFloatArray(), in.createIntArray()); } @Override @@ -2333,7 +2367,8 @@ public final class Display { out.writeInt(mModeId); out.writeInt(mWidth); out.writeInt(mHeight); - out.writeFloat(mRefreshRate); + out.writeFloat(mPeakRefreshRate); + out.writeFloat(mVsyncRate); out.writeFloatArray(mAlternativeRefreshRates); out.writeIntArray(mSupportedHdrTypes); } diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index 42a0c9a9cfe9c26a8b9e4f300cead5de07b6af02..e22207c9771d66ab21b2f0097bdb6c749eda7f8e 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -1796,7 +1796,7 @@ public final class SurfaceControl implements Parcelable { public float yDpi; // Some modes have peak refresh rate lower than the panel vsync rate. - public float refreshRate; + public float peakRefreshRate; // Fixed rate of vsync deadlines for the panel. // This can be higher then the peak refresh rate for some panel technologies // See: VrrConfig.aidl @@ -1820,7 +1820,7 @@ public final class SurfaceControl implements Parcelable { + ", height=" + height + ", xDpi=" + xDpi + ", yDpi=" + yDpi - + ", refreshRate=" + refreshRate + + ", peakRefreshRate=" + peakRefreshRate + ", vsyncRate=" + vsyncRate + ", appVsyncOffsetNanos=" + appVsyncOffsetNanos + ", presentationDeadlineNanos=" + presentationDeadlineNanos @@ -1838,7 +1838,7 @@ public final class SurfaceControl implements Parcelable { && height == that.height && Float.compare(that.xDpi, xDpi) == 0 && Float.compare(that.yDpi, yDpi) == 0 - && Float.compare(that.refreshRate, refreshRate) == 0 + && Float.compare(that.peakRefreshRate, peakRefreshRate) == 0 && Float.compare(that.vsyncRate, vsyncRate) == 0 && appVsyncOffsetNanos == that.appVsyncOffsetNanos && presentationDeadlineNanos == that.presentationDeadlineNanos @@ -1848,7 +1848,7 @@ public final class SurfaceControl implements Parcelable { @Override public int hashCode() { - return Objects.hash(id, width, height, xDpi, yDpi, refreshRate, vsyncRate, + return Objects.hash(id, width, height, xDpi, yDpi, peakRefreshRate, vsyncRate, appVsyncOffsetNanos, presentationDeadlineNanos, group, Arrays.hashCode(supportedHdrTypes)); } diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index 178c0d0d95be807f2096f13bb1e8d71a6d1f7c8c..98335988459a4f83df786097418fabf414d40ee4 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -126,7 +126,7 @@ static struct { jfieldID height; jfieldID xDpi; jfieldID yDpi; - jfieldID refreshRate; + jfieldID peakRefreshRate; jfieldID vsyncRate; jfieldID appVsyncOffsetNanos; jfieldID presentationDeadlineNanos; @@ -1232,7 +1232,7 @@ static jobject convertDisplayModeToJavaObject(JNIEnv* env, const ui::DisplayMode env->SetFloatField(object, gDisplayModeClassInfo.xDpi, config.xDpi); env->SetFloatField(object, gDisplayModeClassInfo.yDpi, config.yDpi); - env->SetFloatField(object, gDisplayModeClassInfo.refreshRate, config.refreshRate); + env->SetFloatField(object, gDisplayModeClassInfo.peakRefreshRate, config.peakRefreshRate); env->SetFloatField(object, gDisplayModeClassInfo.vsyncRate, config.vsyncRate); env->SetLongField(object, gDisplayModeClassInfo.appVsyncOffsetNanos, config.appVsyncOffset); env->SetLongField(object, gDisplayModeClassInfo.presentationDeadlineNanos, @@ -2396,7 +2396,7 @@ int register_android_view_SurfaceControl(JNIEnv* env) gDisplayModeClassInfo.height = GetFieldIDOrDie(env, modeClazz, "height", "I"); gDisplayModeClassInfo.xDpi = GetFieldIDOrDie(env, modeClazz, "xDpi", "F"); gDisplayModeClassInfo.yDpi = GetFieldIDOrDie(env, modeClazz, "yDpi", "F"); - gDisplayModeClassInfo.refreshRate = GetFieldIDOrDie(env, modeClazz, "refreshRate", "F"); + gDisplayModeClassInfo.peakRefreshRate = GetFieldIDOrDie(env, modeClazz, "peakRefreshRate", "F"); gDisplayModeClassInfo.vsyncRate = GetFieldIDOrDie(env, modeClazz, "vsyncRate", "F"); gDisplayModeClassInfo.appVsyncOffsetNanos = GetFieldIDOrDie(env, modeClazz, "appVsyncOffsetNanos", "J"); diff --git a/services/core/java/com/android/server/display/DisplayAdapter.java b/services/core/java/com/android/server/display/DisplayAdapter.java index 4f1df3fb996ec2bd3afd2592302efd039db01e29..70d4ad2c6e1f78e88f1fedc70e33f2f4e5f538c0 100644 --- a/services/core/java/com/android/server/display/DisplayAdapter.java +++ b/services/core/java/com/android/server/display/DisplayAdapter.java @@ -120,14 +120,14 @@ abstract class DisplayAdapter { } public static Display.Mode createMode(int width, int height, float refreshRate) { - return createMode(width, height, refreshRate, new float[0], new int[0]); + return createMode(width, height, refreshRate, refreshRate, new float[0], new int[0]); } - public static Display.Mode createMode(int width, int height, float refreshRate, + public static Display.Mode createMode(int width, int height, float refreshRate, float vsyncRate, float[] alternativeRefreshRates, @Display.HdrCapabilities.HdrType int[] supportedHdrTypes) { return new Display.Mode(NEXT_DISPLAY_MODE_ID.getAndIncrement(), width, height, refreshRate, - alternativeRefreshRates, supportedHdrTypes); + vsyncRate, alternativeRefreshRates, supportedHdrTypes); } public interface Listener { diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index e5f01df75fcdfa79615a2cb261454802d4cd4db4..57b2c24a8b8800508b14b9bf7fce217485a53ba3 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -1132,6 +1132,7 @@ public final class DisplayManagerService extends SystemService { new Display.Mode(Display.DISPLAY_MODE_ID_FOR_FRAME_RATE_OVERRIDE, currentMode.getPhysicalWidth(), currentMode.getPhysicalHeight(), overriddenInfo.refreshRateOverride, + currentMode.getVsyncRate(), new float[0], currentMode.getSupportedHdrTypes()); overriddenInfo.modeId = overriddenInfo.supportedModes[overriddenInfo.supportedModes.length - 1] diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index e5d38cb669d43ddb81fc393dc5bb00dc5e38206c..be3207dfb4eeafcb685b15c4c7fc988528f4bd3e 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -319,10 +319,10 @@ final class LocalDisplayAdapter extends DisplayAdapter { SurfaceControl.DisplayMode other = displayModes[j]; boolean isAlternative = j != i && other.width == mode.width && other.height == mode.height - && other.refreshRate != mode.refreshRate + && other.peakRefreshRate != mode.peakRefreshRate && other.group == mode.group; if (isAlternative) { - alternativeRefreshRates.add(displayModes[j].refreshRate); + alternativeRefreshRates.add(displayModes[j].peakRefreshRate); } } Collections.sort(alternativeRefreshRates); @@ -1360,7 +1360,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { DisplayModeRecord(SurfaceControl.DisplayMode mode, float[] alternativeRefreshRates) { - mMode = createMode(mode.width, mode.height, mode.refreshRate, + mMode = createMode(mode.width, mode.height, mode.peakRefreshRate, mode.vsyncRate, alternativeRefreshRates, mode.supportedHdrTypes); } @@ -1375,7 +1375,9 @@ final class LocalDisplayAdapter extends DisplayAdapter { return mMode.getPhysicalWidth() == mode.width && mMode.getPhysicalHeight() == mode.height && Float.floatToIntBits(mMode.getRefreshRate()) - == Float.floatToIntBits(mode.refreshRate); + == Float.floatToIntBits(mode.peakRefreshRate) + && Float.floatToIntBits(mMode.getVsyncRate()) + == Float.floatToIntBits(mode.vsyncRate); } public String toString() { diff --git a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java index 32e28715cc745f22950ab5f9397690cb1fd68c09..a77a9586fb43143499a79b15b6b29043690f0aba 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java @@ -429,7 +429,7 @@ public class LocalDisplayAdapterTest { SurfaceControl.DisplayMode displayMode = createFakeDisplayMode(0, 1920, 1080, 60f); SurfaceControl.DisplayMode[] modes = new SurfaceControl.DisplayMode[]{displayMode}; - FakeDisplay display = new FakeDisplay(PORT_A, modes, 0, displayMode.refreshRate); + FakeDisplay display = new FakeDisplay(PORT_A, modes, 0, displayMode.peakRefreshRate); setUpDisplay(display); updateAvailableDisplays(); mAdapter.registerLocked(); @@ -451,7 +451,7 @@ public class LocalDisplayAdapterTest { SurfaceControl.DisplayMode displayMode2 = createFakeDisplayMode(1, 1920, 1080, 120f); mListener.addedDisplays.get(0).setUserPreferredDisplayModeLocked( new Display.Mode(displayMode2.width, displayMode2.height, - displayMode2.refreshRate)); + displayMode2.peakRefreshRate)); updateAvailableDisplays(); waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId); @@ -485,7 +485,7 @@ public class LocalDisplayAdapterTest { SurfaceControl.DisplayMode displayMode = createFakeDisplayMode(0, 1920, 1080, 60f); SurfaceControl.DisplayMode[] modes = new SurfaceControl.DisplayMode[]{displayMode}; - FakeDisplay display = new FakeDisplay(PORT_A, modes, 0, displayMode.refreshRate); + FakeDisplay display = new FakeDisplay(PORT_A, modes, 0, displayMode.peakRefreshRate); setUpDisplay(display); updateAvailableDisplays(); mAdapter.registerLocked(); @@ -947,7 +947,7 @@ public class LocalDisplayAdapterTest { // Set the user preferred display mode mListener.addedDisplays.get(0).setUserPreferredDisplayModeLocked( new Display.Mode( - displayMode3.width, displayMode3.height, displayMode3.refreshRate)); + displayMode3.width, displayMode3.height, displayMode3.peakRefreshRate)); updateAvailableDisplays(); waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); displayDeviceInfo = mListener.addedDisplays.get( @@ -991,6 +991,52 @@ public class LocalDisplayAdapterTest { .isTrue(); } + @Test + public void testGetAndSetDisplayModesDisambiguatesByVsyncRate() throws Exception { + SurfaceControl.DisplayMode displayMode1 = createFakeDisplayMode(0, 1920, 1080, 60f, 120f); + SurfaceControl.DisplayMode displayMode2 = createFakeDisplayMode(1, 1920, 1080, 60f, 60f); + SurfaceControl.DisplayMode[] modes = + new SurfaceControl.DisplayMode[]{displayMode1, displayMode2}; + FakeDisplay display = new FakeDisplay(PORT_A, modes, 0, 0); + setUpDisplay(display); + updateAvailableDisplays(); + mAdapter.registerLocked(); + waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); + + assertThat(mListener.addedDisplays.size()).isEqualTo(1); + assertThat(mListener.changedDisplays).isEmpty(); + + DisplayDevice displayDevice = mListener.addedDisplays.get(0); + + DisplayDeviceInfo displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked(); + assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(modes.length); + Display.Mode defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId); + assertThat(matches(defaultMode, displayMode1)).isTrue(); + assertThat(matches(displayDevice.getSystemPreferredDisplayModeLocked(), displayMode1)) + .isTrue(); + + display.dynamicInfo.preferredBootDisplayMode = 1; + setUpDisplay(display); + mInjector.getTransmitter().sendHotplug(display, /* connected */ true); + waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); + + assertTrue(mListener.traversalRequested); + assertThat(mListener.addedDisplays.size()).isEqualTo(1); + assertThat(mListener.changedDisplays.size()).isEqualTo(1); + + DisplayDevice changedDisplayDevice = mListener.changedDisplays.get(0); + changedDisplayDevice.applyPendingDisplayDeviceInfoChangesLocked(); + displayDeviceInfo = changedDisplayDevice.getDisplayDeviceInfoLocked(); + + assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(modes.length); + assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode1); + assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode2); + + assertThat( + matches(changedDisplayDevice.getSystemPreferredDisplayModeLocked(), displayMode2)) + .isTrue(); + } + @Test public void testHdrSdrRatio_notifiesOnChange() throws Exception { FakeDisplay display = new FakeDisplay(PORT_A); @@ -1230,7 +1276,7 @@ public class LocalDisplayAdapterTest { private void assertModeIsSupported(Display.Mode[] supportedModes, SurfaceControl.DisplayMode mode) { assertThat(Arrays.stream(supportedModes).anyMatch( - x -> x.matches(mode.width, mode.height, mode.refreshRate))).isTrue(); + x -> x.matches(mode.width, mode.height, mode.peakRefreshRate))).isTrue(); } private void assertModeIsSupported(Display.Mode[] supportedModes, @@ -1244,7 +1290,7 @@ public class LocalDisplayAdapterTest { + Arrays.toString(supportedModes); Truth.assertWithMessage(message) .that(Arrays.stream(supportedModes) - .anyMatch(x -> x.matches(mode.width, mode.height, mode.refreshRate) + .anyMatch(x -> x.matches(mode.width, mode.height, mode.peakRefreshRate) && Arrays.equals(x.getAlternativeRefreshRates(), sortedAlternativeRates))) .isTrue(); } @@ -1332,16 +1378,28 @@ public class LocalDisplayAdapterTest { private static SurfaceControl.DisplayMode createFakeDisplayMode(int id, int width, int height, float refreshRate) { - return createFakeDisplayMode(id, width, height, refreshRate, /* group */ 0); + return createFakeDisplayMode(id, width, height, refreshRate, refreshRate); + } + + private static SurfaceControl.DisplayMode createFakeDisplayMode(int id, int width, int height, + float refreshRate, + float vsyncRate) { + return createFakeDisplayMode(id, width, height, refreshRate, vsyncRate, /* group */ 0); + } + + private static SurfaceControl.DisplayMode createFakeDisplayMode(int id, int width, int height, + float refreshRate, int group) { + return createFakeDisplayMode(id, width, height, refreshRate, refreshRate, group); } private static SurfaceControl.DisplayMode createFakeDisplayMode(int id, int width, int height, - float refreshRate, int group) { + float refreshRate, float vsyncRate, int group) { final SurfaceControl.DisplayMode mode = new SurfaceControl.DisplayMode(); mode.id = id; mode.width = width; mode.height = height; - mode.refreshRate = refreshRate; + mode.peakRefreshRate = refreshRate; + mode.vsyncRate = vsyncRate; mode.xDpi = 100; mode.yDpi = 100; mode.group = group; @@ -1444,6 +1502,9 @@ public class LocalDisplayAdapterTest { private boolean matches(Display.Mode a, SurfaceControl.DisplayMode b) { return a.getPhysicalWidth() == b.width && a.getPhysicalHeight() == b.height - && Float.floatToIntBits(a.getRefreshRate()) == Float.floatToIntBits(b.refreshRate); + && Float.floatToIntBits(a.getRefreshRate()) + == Float.floatToIntBits(b.peakRefreshRate) + && Float.floatToIntBits(a.getVsyncRate()) + == Float.floatToIntBits(b.vsyncRate); } }