diff options
| -rw-r--r-- | api/current.txt | 1 | ||||
| -rw-r--r-- | core/api/current.txt | 1 | ||||
| -rw-r--r-- | core/java/android/view/Display.java | 44 | ||||
| -rw-r--r-- | services/core/java/com/android/server/display/DisplayAdapter.java | 9 | ||||
| -rw-r--r-- | services/core/java/com/android/server/display/LocalDisplayAdapter.java | 52 | ||||
| -rw-r--r-- | services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java | 121 |
6 files changed, 216 insertions, 12 deletions
diff --git a/api/current.txt b/api/current.txt index 2453b6722995..cefae3fdf844 100644 --- a/api/current.txt +++ b/api/current.txt @@ -52665,6 +52665,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 0516f6303960..0bbd16a541c5 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -50772,6 +50772,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 237ed729e0b8..3021aa6a0783 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); } /** @@ -1464,6 +1477,28 @@ public final class Display { } /** + * Returns an array of refresh rates which can be switched to seamlessly. + * <p> + * A seamless switch is one without visual interruptions, such as a black screen for + * a second or two. + * <p> + * 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. + * <p> + * 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. * * @hide @@ -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 838dc84d4a0a..1fc151221b46 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 9245f55bd9fe..f6578584cb18 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]; + List<Float> alternativeRefreshRates = new ArrayList<>(); + for (int j = 0; j < configs.length; j++) { + SurfaceControl.DisplayConfig other = configs[j]; + boolean isAlternative = j != i && other.width == config.width + && other.height == config.height + && other.refreshRate != config.refreshRate + && other.configGroup == config.configGroup; + if (isAlternative) { + alternativeRefreshRates.add(configs[j].refreshRate); + } + } + Collections.sort(alternativeRefreshRates); + // First, check to see if we've already added a matching mode. Since not all // configuration options are exposed via Display.Mode, it's possible that we have // multiple DisplayConfigs that would generate the same Display.Mode. boolean existingMode = false; - for (int j = 0; j < records.size(); j++) { - if (records.get(j).hasMatchingMode(config)) { + for (DisplayModeRecord record : records) { + if (record.hasMatchingMode(config) + && refreshRatesEquals(alternativeRefreshRates, + record.mMode.getAlternativeRefreshRates())) { existingMode = true; break; } @@ -282,9 +297,13 @@ final class LocalDisplayAdapter extends DisplayAdapter { // If we haven't already added a mode for this configuration to the new set of // supported modes then check to see if we have one in the prior set of supported // modes to reuse. - DisplayModeRecord record = findDisplayModeRecord(config); + DisplayModeRecord record = findDisplayModeRecord(config, alternativeRefreshRates); if (record == null) { - record = new DisplayModeRecord(config); + float[] alternativeRates = new float[alternativeRefreshRates.size()]; + for (int j = 0; j < alternativeRates.length; j++) { + alternativeRates[j] = alternativeRefreshRates.get(j); + } + record = new DisplayModeRecord(config, alternativeRates); modesAdded = true; } records.add(record); @@ -495,16 +514,31 @@ final class LocalDisplayAdapter extends DisplayAdapter { return true; } - private DisplayModeRecord findDisplayModeRecord(SurfaceControl.DisplayConfig config) { + private DisplayModeRecord findDisplayModeRecord(SurfaceControl.DisplayConfig config, + List<Float> alternativeRefreshRates) { for (int i = 0; i < mSupportedModes.size(); i++) { DisplayModeRecord record = mSupportedModes.valueAt(i); - if (record.hasMatchingMode(config)) { + if (record.hasMatchingMode(config) + && refreshRatesEquals(alternativeRefreshRates, + record.mMode.getAlternativeRefreshRates())) { return record; } } return null; } + private boolean refreshRatesEquals(List<Float> list, float[] array) { + if (list.size() != array.length) { + return false; + } + for (int i = 0; i < list.size(); i++) { + if (Float.floatToIntBits(list.get(i)) != Float.floatToIntBits(array[i])) { + return false; + } + } + return true; + } + @Override public void applyPendingDisplayDeviceInfoChangesLocked() { if (mHavePendingChanges) { @@ -1032,8 +1066,10 @@ final class LocalDisplayAdapter extends DisplayAdapter { private static final class DisplayModeRecord { public final Display.Mode mMode; - DisplayModeRecord(SurfaceControl.DisplayConfig config) { - mMode = createMode(config.width, config.height, config.refreshRate); + DisplayModeRecord(SurfaceControl.DisplayConfig config, + float[] alternativeRefreshRates) { + mMode = createMode(config.width, config.height, config.refreshRate, + alternativeRefreshRates); } /** 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 6a6fb82897bb..dbdee979b6a4 100644 --- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java @@ -42,6 +42,8 @@ import com.android.dx.mockito.inline.extended.StaticMockitoSession; import com.android.server.LocalServices; import com.android.server.lights.LightsManager; +import com.google.common.truth.Truth; + import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -190,6 +192,102 @@ public class LocalDisplayAdapterTest { 16000); } + private static class DisplayModeWrapper { + public SurfaceControl.DisplayConfig config; + public float[] expectedAlternativeRefreshRates; + + DisplayModeWrapper(SurfaceControl.DisplayConfig config, + float[] expectedAlternativeRefreshRates) { + this.config = config; + this.expectedAlternativeRefreshRates = expectedAlternativeRefreshRates; + } + } + + /** + * Updates the <code>display</code> using the given <code>modes</code> and then checks if the + * <code>expectedAlternativeRefreshRates</code> are present for each of the + * <code>modes</code>. + */ + 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; } |