diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index 858cce49d1c10..8340460621915 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -69,21 +69,27 @@ final class LocalDisplayAdapter extends DisplayAdapter { private final LongSparseArray mDevices = new LongSparseArray<>(); - @SuppressWarnings("unused") // Becomes active at instantiation time. - private PhysicalDisplayEventReceiver mPhysicalDisplayEventReceiver; + private final Injector mInjector; // Called with SyncRoot lock held. public LocalDisplayAdapter(DisplayManagerService.SyncRoot syncRoot, Context context, Handler handler, Listener listener) { + this(syncRoot, context, handler, listener, new Injector()); + } + + LocalDisplayAdapter(DisplayManagerService.SyncRoot syncRoot, + Context context, Handler handler, Listener listener, Injector injector) { super(syncRoot, context, handler, listener, TAG); + mInjector = injector; } @Override public void registerLocked() { super.registerLocked(); - mPhysicalDisplayEventReceiver = new PhysicalDisplayEventReceiver(getHandler().getLooper()); + mInjector.setDisplayEventListenerLocked(getHandler().getLooper(), + new LocalDisplayEventListener()); for (long physicalDisplayId : SurfaceControl.getPhysicalDisplayIds()) { tryConnectDisplayLocked(physicalDisplayId); @@ -1052,12 +1058,33 @@ final class LocalDisplayAdapter extends DisplayAdapter { } } - private final class PhysicalDisplayEventReceiver extends DisplayEventReceiver { - PhysicalDisplayEventReceiver(Looper looper) { - super(looper, VSYNC_SOURCE_APP, CONFIG_CHANGED_EVENT_DISPATCH); + public static class Injector { + private ProxyDisplayEventReceiver mReceiver; + public void setDisplayEventListenerLocked(Looper looper, DisplayEventListener listener) { + mReceiver = new ProxyDisplayEventReceiver(looper, listener); } + } - @Override + public interface DisplayEventListener { + void onHotplug(long timestampNanos, long physicalDisplayId, boolean connected); + void onConfigChanged(long timestampNanos, long physicalDisplayId, int configId); + } + + public static final class ProxyDisplayEventReceiver extends DisplayEventReceiver { + private final DisplayEventListener mListener; + ProxyDisplayEventReceiver(Looper looper, DisplayEventListener listener) { + super(looper, VSYNC_SOURCE_APP, CONFIG_CHANGED_EVENT_DISPATCH); + mListener = listener; + } + public void onHotplug(long timestampNanos, long physicalDisplayId, boolean connected) { + mListener.onHotplug(timestampNanos, physicalDisplayId, connected); + } + public void onConfigChanged(long timestampNanos, long physicalDisplayId, int configId) { + mListener.onConfigChanged(timestampNanos, physicalDisplayId, configId); + } + } + + private final class LocalDisplayEventListener implements DisplayEventListener { public void onHotplug(long timestampNanos, long physicalDisplayId, boolean connected) { synchronized (getSyncRoot()) { if (connected) { @@ -1067,8 +1094,6 @@ final class LocalDisplayAdapter extends DisplayAdapter { } } } - - @Override public void onConfigChanged(long timestampNanos, long physicalDisplayId, int configId) { if (DEBUG) { Slog.d(TAG, "onConfigChanged(" 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 9d2393b4a8f7f..0ab15017a2b46 100644 --- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java @@ -83,6 +83,8 @@ public class LocalDisplayAdapterTest { private LinkedList mAddresses = new LinkedList<>(); + private Injector mInjector; + @Before public void setUp() throws Exception { mMockitoSession = mockitoSession() @@ -94,8 +96,9 @@ public class LocalDisplayAdapterTest { doReturn(mMockedResources).when(mMockedContext).getResources(); LocalServices.removeServiceForTest(LightsManager.class); LocalServices.addService(LightsManager.class, mMockedLightsManager); + mInjector = new Injector(); mAdapter = new LocalDisplayAdapter(mMockedSyncRoot, mMockedContext, mHandler, - mListener); + mListener, mInjector); spyOn(mAdapter); doReturn(mMockedContext).when(mAdapter).getOverlayContext(); } @@ -222,7 +225,7 @@ public class LocalDisplayAdapterTest { display.configs = configs; display.activeConfig = 1; setUpDisplay(display); - mAdapter.registerLocked(); + mInjector.getTransmitter().sendHotplug(display, /* connected */ true); waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); assertThat(SurfaceControl.getActiveConfig(display.token)).isEqualTo(1); @@ -272,7 +275,7 @@ public class LocalDisplayAdapterTest { new int[Display.HdrCapabilities.HDR_TYPE_HDR10_PLUS], 1000, 1000, 0); display.hdrCapabilities = changedHdrCapabilities; setUpDisplay(display); - mAdapter.registerLocked(); + mInjector.getTransmitter().sendHotplug(display, /* connected */ true); waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); assertThat(mListener.addedDisplays.size()).isEqualTo(1); @@ -308,7 +311,7 @@ public class LocalDisplayAdapterTest { final int[] changedColorModes = new int[]{Display.COLOR_MODE_DEFAULT}; display.colorModes = changedColorModes; setUpDisplay(display); - mAdapter.registerLocked(); + mInjector.getTransmitter().sendHotplug(display, /* connected */ true); waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); assertThat(mListener.addedDisplays.size()).isEqualTo(1); @@ -322,6 +325,35 @@ public class LocalDisplayAdapterTest { assertThat(displayDeviceInfo.supportedColorModes).isEqualTo(changedColorModes); } + @Test + public void testDisplayChange_withStaleDesiredDisplayConfigSpecs() throws Exception { + SurfaceControl.DisplayConfig[] configs = new SurfaceControl.DisplayConfig[]{ + createFakeDisplayConfig(1920, 1080, 60f), + createFakeDisplayConfig(1920, 1080, 50f) + }; + final int activeConfig = 0; + FakeDisplay display = new FakeDisplay(PORT_A, configs, activeConfig); + display.desiredDisplayConfigSpecs.defaultConfig = 1; + + setUpDisplay(display); + updateAvailableDisplays(); + mAdapter.registerLocked(); + waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); + + // Change the display + display.configs = new SurfaceControl.DisplayConfig[]{ + createFakeDisplayConfig(1920, 1080, 60f) + }; + // SurfaceFlinger can return a stale defaultConfig. Make sure this doesn't + // trigger ArrayOutOfBoundsException. + display.desiredDisplayConfigSpecs.defaultConfig = 1; + + setUpDisplay(display); + updateAvailableDisplays(); + mInjector.getTransmitter().sendHotplug(display, /* connected */ true); + waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); + } + private void assertDisplayDpi(DisplayDeviceInfo info, int expectedPort, float expectedXdpi, float expectedYDpi, @@ -356,6 +388,8 @@ public class LocalDisplayAdapterTest { public int[] colorModes = new int[]{ Display.COLOR_MODE_DEFAULT }; public Display.HdrCapabilities hdrCapabilities = new Display.HdrCapabilities(new int[0], 1000, 1000, 0); + public SurfaceControl.DesiredDisplayConfigSpecs desiredDisplayConfigSpecs = + new SurfaceControl.DesiredDisplayConfigSpecs(0, 60.f, 60.f, 60.f, 60.f); private FakeDisplay(int port) { this.address = createDisplayAddress(port); @@ -387,7 +421,7 @@ public class LocalDisplayAdapterTest { () -> SurfaceControl.getDisplayColorModes(display.token)); doReturn(display.hdrCapabilities).when( () -> SurfaceControl.getHdrCapabilities(display.token)); - doReturn(new SurfaceControl.DesiredDisplayConfigSpecs(0, 60.f, 60.f, 60.f, 60.f)) + doReturn(display.desiredDisplayConfigSpecs) .when(() -> SurfaceControl.getDesiredDisplayConfigSpecs(display.token)); } @@ -422,7 +456,7 @@ public class LocalDisplayAdapterTest { return config; } - private void waitForHandlerToComplete(Handler handler, long waitTimeMs) + private static void waitForHandlerToComplete(Handler handler, long waitTimeMs) throws InterruptedException { final Object lock = new Object(); synchronized (lock) { @@ -435,6 +469,35 @@ public class LocalDisplayAdapterTest { } } + private class HotplugTransmitter { + private final Handler mHandler; + private final LocalDisplayAdapter.DisplayEventListener mListener; + + HotplugTransmitter(Looper looper, LocalDisplayAdapter.DisplayEventListener listener) { + mHandler = new Handler(looper); + mListener = listener; + } + + public void sendHotplug(FakeDisplay display, boolean connected) + throws InterruptedException { + mHandler.post(() -> mListener.onHotplug(/* timestampNanos = */ 0, + display.address.getPhysicalDisplayId(), connected)); + waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); + } + } + + private class Injector extends LocalDisplayAdapter.Injector { + private HotplugTransmitter mTransmitter; + @Override + public void setDisplayEventListenerLocked(Looper looper, + LocalDisplayAdapter.DisplayEventListener listener) { + mTransmitter = new HotplugTransmitter(looper, listener); + } + public HotplugTransmitter getTransmitter() { + return mTransmitter; + } + } + private class TestListener implements DisplayAdapter.Listener { public ArrayList addedDisplays = new ArrayList<>(); public ArrayList changedDisplays = new ArrayList<>();