diff --git a/api/current.txt b/api/current.txt index 74f108dcd6a4a..88e09d5e128ee 100644 --- a/api/current.txt +++ b/api/current.txt @@ -52693,6 +52693,7 @@ package android.view { public static final class Display.Mode implements android.os.Parcelable { method public int describeContents(); + method @NonNull public float[] getAlternativeRefreshRates(); method public int getModeId(); method public int getPhysicalHeight(); method public int getPhysicalWidth(); diff --git a/core/api/current.txt b/core/api/current.txt index 51eb78f097a2f..434705d1c5468 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -50798,6 +50798,7 @@ package android.view { public static final class Display.Mode implements android.os.Parcelable { method public int describeContents(); + method @NonNull public float[] getAlternativeRefreshRates(); method public int getModeId(); method public int getPhysicalHeight(); method public int getPhysicalWidth(); diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index 237ed729e0b8c..3021aa6a07835 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -1405,16 +1405,29 @@ public final class Display { private final int mWidth; private final int mHeight; private final float mRefreshRate; + @NonNull + private final float[] mAlternativeRefreshRates; /** * @hide */ @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]); + } + + /** + * @hide + */ + public Mode(int modeId, int width, int height, float refreshRate, + float[] alternativeRefreshRates) { mModeId = modeId; mWidth = width; mHeight = height; mRefreshRate = refreshRate; + mAlternativeRefreshRates = + Arrays.copyOf(alternativeRefreshRates, alternativeRefreshRates.length); + Arrays.sort(mAlternativeRefreshRates); } /** @@ -1463,6 +1476,28 @@ public final class Display { return mRefreshRate; } + /** + * Returns an array of refresh rates which can be switched to seamlessly. + *
+ * A seamless switch is one without visual interruptions, such as a black screen for + * a second or two. + *
+ * Presence in this list does not guarantee a switch will occur to the desired + * refresh rate, but rather, if a switch does occur to a refresh rate in this list, + * it is guaranteed to be seamless. + *
+ * The binary relation "refresh rate X is alternative to Y" is non-reflexive,
+ * symmetric and transitive. For example the mode 1920x1080 60Hz, will never have an
+ * alternative refresh rate of 60Hz. If 1920x1080 60Hz has an alternative of 50Hz
+ * then 1920x1080 50Hz will have alternative refresh rate of 60Hz. If 1920x1080 60Hz
+ * has an alternative of 50Hz and 1920x1080 50Hz has an alternative of 24Hz, then 1920x1080
+ * 60Hz will also have an alternative of 24Hz.
+ */
+ @NonNull
+ public float[] getAlternativeRefreshRates() {
+ return mAlternativeRefreshRates;
+ }
+
/**
* Returns {@code true} if this mode matches the given parameters.
*
@@ -1483,7 +1518,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.mRefreshRate)
+ && Arrays.equals(mAlternativeRefreshRates, that.mAlternativeRefreshRates);
}
@Override
@@ -1493,6 +1529,7 @@ public final class Display {
hash = hash * 17 + mWidth;
hash = hash * 17 + mHeight;
hash = hash * 17 + Float.floatToIntBits(mRefreshRate);
+ hash = hash * 17 + Arrays.hashCode(mAlternativeRefreshRates);
return hash;
}
@@ -1503,6 +1540,8 @@ public final class Display {
.append(", width=").append(mWidth)
.append(", height=").append(mHeight)
.append(", fps=").append(mRefreshRate)
+ .append(", alternativeRefreshRates=")
+ .append(Arrays.toString(mAlternativeRefreshRates))
.append("}")
.toString();
}
@@ -1513,7 +1552,7 @@ public final class Display {
}
private Mode(Parcel in) {
- this(in.readInt(), in.readInt(), in.readInt(), in.readFloat());
+ this(in.readInt(), in.readInt(), in.readInt(), in.readFloat(), in.createFloatArray());
}
@Override
@@ -1522,6 +1561,7 @@ public final class Display {
out.writeInt(mWidth);
out.writeInt(mHeight);
out.writeFloat(mRefreshRate);
+ out.writeFloatArray(mAlternativeRefreshRates);
}
@SuppressWarnings("hiding")
diff --git a/services/core/java/com/android/server/display/DisplayAdapter.java b/services/core/java/com/android/server/display/DisplayAdapter.java
index 838dc84d4a0a6..1fc151221b461 100644
--- a/services/core/java/com/android/server/display/DisplayAdapter.java
+++ b/services/core/java/com/android/server/display/DisplayAdapter.java
@@ -120,8 +120,13 @@ abstract class DisplayAdapter {
}
public static Display.Mode createMode(int width, int height, float refreshRate) {
- return new Display.Mode(
- NEXT_DISPLAY_MODE_ID.getAndIncrement(), width, height, refreshRate);
+ return createMode(width, height, refreshRate, new float[0]);
+ }
+
+ public static Display.Mode createMode(int width, int height, float refreshRate,
+ float[] alternativeRefreshRates) {
+ return new Display.Mode(NEXT_DISPLAY_MODE_ID.getAndIncrement(), width, height, refreshRate,
+ alternativeRefreshRates);
}
public interface Listener {
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 9245f55bd9fe1..f6578584cb188 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -266,12 +266,27 @@ final class LocalDisplayAdapter extends DisplayAdapter {
boolean modesAdded = false;
for (int i = 0; i < configs.length; i++) {
SurfaceControl.DisplayConfig config = configs[i];
+ Listdisplay using the given modes and then checks if the
+ * expectedAlternativeRefreshRates are present for each of the
+ * modes.
+ */
+ private void testAlternativeRefreshRatesCommon(FakeDisplay display, DisplayModeWrapper[] modes)
+ throws InterruptedException {
+ // Update the display.
+ SurfaceControl.DisplayConfig[] configs = new SurfaceControl.DisplayConfig[modes.length];
+ for (int i = 0; i < modes.length; i++) {
+ configs[i] = modes[i].config;
+ }
+ display.configs = configs;
+ setUpDisplay(display);
+ mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ assertThat(mListener.changedDisplays.size()).isGreaterThan(0);
+
+ // Verify the supported modes are updated accordingly.
+ DisplayDevice displayDevice =
+ mListener.changedDisplays.get(mListener.changedDisplays.size() - 1);
+ displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
+ Display.Mode[] supportedModes = displayDevice.getDisplayDeviceInfoLocked().supportedModes;
+ assertThat(supportedModes.length).isEqualTo(configs.length);
+
+ for (int i = 0; i < modes.length; i++) {
+ assertModeIsSupported(supportedModes, configs[i],
+ modes[i].expectedAlternativeRefreshRates);
+ }
+ }
+
+ @Test
+ public void testAfterDisplayChange_AlternativeRefreshRatesAreUpdated() throws Exception {
+ FakeDisplay display = new FakeDisplay(PORT_A);
+ setUpDisplay(display);
+ updateAvailableDisplays();
+ mAdapter.registerLocked();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+ testAlternativeRefreshRatesCommon(display, new DisplayModeWrapper[] {
+ new DisplayModeWrapper(
+ createFakeDisplayConfig(1920, 1080, 60f, 0), new float[]{24f, 50f}),
+ new DisplayModeWrapper(
+ createFakeDisplayConfig(1920, 1080, 50f, 0), new float[]{24f, 60f}),
+ new DisplayModeWrapper(
+ createFakeDisplayConfig(1920, 1080, 24f, 0), new float[]{50f, 60f}),
+ new DisplayModeWrapper(
+ createFakeDisplayConfig(3840, 2160, 60f, 0), new float[]{24f, 50f}),
+ new DisplayModeWrapper(
+ createFakeDisplayConfig(3840, 2160, 50f, 0), new float[]{24f, 60f}),
+ new DisplayModeWrapper(
+ createFakeDisplayConfig(3840, 2160, 24f, 0), new float[]{50f, 60f}),
+ });
+
+ testAlternativeRefreshRatesCommon(display, new DisplayModeWrapper[] {
+ new DisplayModeWrapper(
+ createFakeDisplayConfig(1920, 1080, 60f, 0), new float[]{50f}),
+ new DisplayModeWrapper(
+ createFakeDisplayConfig(1920, 1080, 50f, 0), new float[]{60f}),
+ new DisplayModeWrapper(
+ createFakeDisplayConfig(1920, 1080, 24f, 1), new float[0]),
+ new DisplayModeWrapper(
+ createFakeDisplayConfig(3840, 2160, 60f, 2), new float[0]),
+ new DisplayModeWrapper(
+ createFakeDisplayConfig(3840, 2160, 50f, 3), new float[]{24f}),
+ new DisplayModeWrapper(
+ createFakeDisplayConfig(3840, 2160, 24f, 3), new float[]{50f}),
+ });
+
+ testAlternativeRefreshRatesCommon(display, new DisplayModeWrapper[] {
+ new DisplayModeWrapper(
+ createFakeDisplayConfig(1920, 1080, 60f, 0), new float[0]),
+ new DisplayModeWrapper(
+ createFakeDisplayConfig(1920, 1080, 50f, 1), new float[0]),
+ new DisplayModeWrapper(
+ createFakeDisplayConfig(1920, 1080, 24f, 2), new float[0]),
+ new DisplayModeWrapper(
+ createFakeDisplayConfig(3840, 2160, 60f, 3), new float[0]),
+ new DisplayModeWrapper(
+ createFakeDisplayConfig(3840, 2160, 50f, 4), new float[0]),
+ new DisplayModeWrapper(
+ createFakeDisplayConfig(3840, 2160, 24f, 5), new float[0]),
+ });
+ }
+
@Test
public void testAfterDisplayChange_DisplayModesAreUpdated() throws Exception {
SurfaceControl.DisplayConfig displayConfig = createFakeDisplayConfig(1920, 1080, 60f);
@@ -419,6 +517,23 @@ public class LocalDisplayAdapterTest {
x -> x.matches(mode.width, mode.height, mode.refreshRate))).isTrue();
}
+ private void assertModeIsSupported(Display.Mode[] supportedModes,
+ SurfaceControl.DisplayConfig mode, float[] alternativeRefreshRates) {
+ float[] sortedAlternativeRates =
+ Arrays.copyOf(alternativeRefreshRates, alternativeRefreshRates.length);
+ Arrays.sort(sortedAlternativeRates);
+
+ String message = "Expected " + mode + " with alternativeRefreshRates = "
+ + Arrays.toString(alternativeRefreshRates) + " to be in list of supported modes = "
+ + Arrays.toString(supportedModes);
+ Truth.assertWithMessage(message)
+ .that(Arrays.stream(supportedModes)
+ .anyMatch(x -> x.matches(mode.width, mode.height, mode.refreshRate)
+ && Arrays.equals(x.getAlternativeRefreshRates(), sortedAlternativeRates)))
+ .isTrue();
+ }
+
+
private static class FakeDisplay {
public final DisplayAddress.Physical address;
public final IBinder token = new Binder();
@@ -492,12 +607,18 @@ public class LocalDisplayAdapterTest {
private static SurfaceControl.DisplayConfig createFakeDisplayConfig(int width, int height,
float refreshRate) {
+ return createFakeDisplayConfig(width, height, refreshRate, 0);
+ }
+
+ private static SurfaceControl.DisplayConfig createFakeDisplayConfig(int width, int height,
+ float refreshRate, int configGroup) {
final SurfaceControl.DisplayConfig config = new SurfaceControl.DisplayConfig();
config.width = width;
config.height = height;
config.refreshRate = refreshRate;
config.xDpi = 100;
config.yDpi = 100;
+ config.configGroup = configGroup;
return config;
}