/* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.display; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeTrue; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.when; import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Rect; import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.view.Display; import android.view.DisplayAddress; import android.view.SurfaceControl; import android.view.SurfaceControl.RefreshRateRange; import android.view.SurfaceControl.RefreshRateRanges; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.dx.mockito.inline.extended.StaticMockitoSession; import com.android.internal.R; import com.android.server.LocalServices; import com.android.server.display.LocalDisplayAdapter.BacklightAdapter; import com.android.server.display.mode.DisplayModeDirector; import com.android.server.lights.LightsManager; import com.android.server.lights.LogicalLight; import com.google.common.truth.Truth; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.quality.Strictness; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @SmallTest @RunWith(AndroidJUnit4.class) public class LocalDisplayAdapterTest { private static final Long DISPLAY_MODEL = Long.valueOf(0xAAAAAAAAL); private static final int PORT_A = 0; private static final int PORT_B = 0x80; private static final int PORT_C = 0xFF; private static final float REFRESH_RATE = 60f; private static final RefreshRateRange REFRESH_RATE_RANGE = new RefreshRateRange(REFRESH_RATE, REFRESH_RATE); private static final RefreshRateRanges REFRESH_RATE_RANGES = new RefreshRateRanges(REFRESH_RATE_RANGE, REFRESH_RATE_RANGE); private static final long HANDLER_WAIT_MS = 100; private static final int[] HDR_TYPES = new int[]{1, 2}; private StaticMockitoSession mMockitoSession; private LocalDisplayAdapter mAdapter; @Mock private DisplayManagerService.SyncRoot mMockedSyncRoot; @Mock private Context mMockedContext; @Mock private Resources mMockedResources; @Mock private LightsManager mMockedLightsManager; @Mock private LogicalLight mMockedBacklight; private Handler mHandler; private TestListener mListener = new TestListener(); private LinkedList mAddresses = new LinkedList<>(); private Injector mInjector; @Mock private LocalDisplayAdapter.SurfaceControlProxy mSurfaceControlProxy; private static final float[] DISPLAY_RANGE_NITS = { 2.685f, 478.5f }; private static final int[] BACKLIGHT_RANGE = { 1, 255 }; private static final float[] BACKLIGHT_RANGE_ZERO_TO_ONE = { 0.0f, 1.0f }; @Before public void setUp() throws Exception { mMockitoSession = mockitoSession() .initMocks(this) .strictness(Strictness.LENIENT) .startMocking(); mHandler = new Handler(Looper.getMainLooper()); doReturn(mMockedResources).when(mMockedContext).getResources(); LocalServices.removeServiceForTest(LightsManager.class); LocalServices.addService(LightsManager.class, mMockedLightsManager); mInjector = new Injector(); when(mSurfaceControlProxy.getBootDisplayModeSupport()).thenReturn(true); mAdapter = new LocalDisplayAdapter(mMockedSyncRoot, mMockedContext, mHandler, mListener, mInjector); spyOn(mAdapter); doReturn(mMockedContext).when(mAdapter).getOverlayContext(); TypedArray mockNitsRange = createFloatTypedArray(DISPLAY_RANGE_NITS); when(mMockedResources.obtainTypedArray(R.array.config_screenBrightnessNits)) .thenReturn(mockNitsRange); when(mMockedResources.getIntArray(R.array.config_screenBrightnessBacklight)) .thenReturn(BACKLIGHT_RANGE); when(mMockedResources.getFloat(com.android.internal.R.dimen .config_screenBrightnessSettingMinimumFloat)) .thenReturn(BACKLIGHT_RANGE_ZERO_TO_ONE[0]); when(mMockedResources.getFloat(com.android.internal.R.dimen .config_screenBrightnessSettingMaximumFloat)) .thenReturn(BACKLIGHT_RANGE_ZERO_TO_ONE[1]); when(mMockedResources.getStringArray(R.array.config_displayUniqueIdArray)) .thenReturn(new String[]{}); TypedArray mockArray = mock(TypedArray.class); when(mockArray.length()).thenReturn(0); when(mMockedResources.obtainTypedArray(R.array.config_maskBuiltInDisplayCutoutArray)) .thenReturn(mockArray); when(mMockedResources.obtainTypedArray(R.array.config_waterfallCutoutArray)) .thenReturn(mockArray); when(mMockedResources.obtainTypedArray(R.array.config_roundedCornerRadiusArray)) .thenReturn(mockArray); when(mMockedResources.obtainTypedArray(R.array.config_roundedCornerTopRadiusArray)) .thenReturn(mockArray); when(mMockedResources.obtainTypedArray(R.array.config_roundedCornerBottomRadiusArray)) .thenReturn(mockArray); when(mMockedResources.obtainTypedArray( com.android.internal.R.array.config_autoBrightnessDisplayValuesNits)) .thenReturn(mockArray); when(mMockedResources.getIntArray( com.android.internal.R.array.config_autoBrightnessLevels)) .thenReturn(new int[]{}); when(mMockedResources.obtainTypedArray(R.array.config_displayShapeArray)) .thenReturn(mockArray); when(mMockedResources.obtainTypedArray(R.array.config_builtInDisplayIsRoundArray)) .thenReturn(mockArray); when(mMockedResources.getIntArray( com.android.internal.R.array.config_brightnessThresholdsOfPeakRefreshRate)) .thenReturn(new int[]{}); when(mMockedResources.getIntArray( com.android.internal.R.array.config_ambientThresholdsOfPeakRefreshRate)) .thenReturn(new int[]{}); when(mMockedResources.getIntArray( com.android.internal.R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate)) .thenReturn(new int[]{}); when(mMockedResources.getIntArray( com.android.internal.R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate)) .thenReturn(new int[]{}); } @After public void tearDown() { if (mMockitoSession != null) { mMockitoSession.finishMocking(); } } /** * Confirm that display is marked as private when it is listed in * com.android.internal.R.array.config_localPrivateDisplayPorts. */ @Test public void testPrivateDisplay() throws Exception { setUpDisplay(new FakeDisplay(PORT_A)); setUpDisplay(new FakeDisplay(PORT_B)); setUpDisplay(new FakeDisplay(PORT_C)); updateAvailableDisplays(); doReturn(new int[]{ PORT_B }).when(mMockedResources) .getIntArray(com.android.internal.R.array.config_localPrivateDisplayPorts); mAdapter.registerLocked(); waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); // This should be public assertDisplayPrivateFlag(mListener.addedDisplays.get(0).getDisplayDeviceInfoLocked(), PORT_A, false); // This should be private assertDisplayPrivateFlag(mListener.addedDisplays.get(1).getDisplayDeviceInfoLocked(), PORT_B, true); // This should be public assertDisplayPrivateFlag(mListener.addedDisplays.get(2).getDisplayDeviceInfoLocked(), PORT_C, false); } @Test public void testSupportedDisplayModesGetOverriddenWhenDisplayIsUpdated() throws InterruptedException { SurfaceControl.DisplayMode displayMode = createFakeDisplayMode(0, 1920, 1080, 0); displayMode.supportedHdrTypes = new int[0]; FakeDisplay display = new FakeDisplay(PORT_A, new SurfaceControl.DisplayMode[]{displayMode}, 0, 0); setUpDisplay(display); updateAvailableDisplays(); mAdapter.registerLocked(); waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); DisplayDevice displayDevice = mListener.addedDisplays.get(0); displayDevice.applyPendingDisplayDeviceInfoChangesLocked(); Display.Mode[] supportedModes = displayDevice.getDisplayDeviceInfoLocked().supportedModes; Assert.assertEquals(1, supportedModes.length); Assert.assertEquals(0, supportedModes[0].getSupportedHdrTypes().length); displayMode.supportedHdrTypes = new int[]{3, 2}; display.dynamicInfo.supportedDisplayModes = new SurfaceControl.DisplayMode[]{displayMode}; setUpDisplay(display); mInjector.getTransmitter().sendHotplug(display, true); waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); displayDevice = mListener.changedDisplays.get(0); displayDevice.applyPendingDisplayDeviceInfoChangesLocked(); supportedModes = displayDevice.getDisplayDeviceInfoLocked().supportedModes; Assert.assertEquals(1, supportedModes.length); assertArrayEquals(new int[]{2, 3}, supportedModes[0].getSupportedHdrTypes()); } /** * Confirm that all local displays are public when config_localPrivateDisplayPorts is empty. */ @Test public void testPublicDisplaysForNoConfigLocalPrivateDisplayPorts() throws Exception { setUpDisplay(new FakeDisplay(PORT_A)); setUpDisplay(new FakeDisplay(PORT_C)); updateAvailableDisplays(); // config_localPrivateDisplayPorts is null mAdapter.registerLocked(); waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); // This should be public assertDisplayPrivateFlag(mListener.addedDisplays.get(0).getDisplayDeviceInfoLocked(), PORT_A, false); // This should be public assertDisplayPrivateFlag(mListener.addedDisplays.get(1).getDisplayDeviceInfoLocked(), PORT_C, false); } private static void assertDisplayPrivateFlag( DisplayDeviceInfo info, int expectedPort, boolean shouldBePrivate) { final DisplayAddress.Physical address = (DisplayAddress.Physical) info.address; assertNotNull(address); assertEquals(expectedPort, address.getPort()); assertEquals(DISPLAY_MODEL, address.getModel()); assertEquals(shouldBePrivate, (info.flags & DisplayDeviceInfo.FLAG_PRIVATE) != 0); } /** * Confirm that external display uses physical density. */ @Test public void testDpiValues() throws Exception { // needs default one always setUpDisplay(new FakeDisplay(PORT_A)); setUpDisplay(new FakeDisplay(PORT_B)); updateAvailableDisplays(); mAdapter.registerLocked(); waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); assertDisplayDpi( mListener.addedDisplays.get(0).getDisplayDeviceInfoLocked(), PORT_A, 100, 100, 16000); assertDisplayDpi( mListener.addedDisplays.get(1).getDisplayDeviceInfoLocked(), PORT_B, 100, 100, 16000); } private static class DisplayModeWrapper { public SurfaceControl.DisplayMode mode; public float[] expectedAlternativeRefreshRates; DisplayModeWrapper(SurfaceControl.DisplayMode mode, float[] expectedAlternativeRefreshRates) { this.mode = mode; this.expectedAlternativeRefreshRates = expectedAlternativeRefreshRates; } } /** * Updates the display using the given modes and then checks if the * expectedAlternativeRefreshRates are present for each of the * modes. */ private void testAlternativeRefreshRatesCommon(FakeDisplay display, DisplayModeWrapper[] wrappedModes) throws InterruptedException { // Update the display. SurfaceControl.DisplayMode[] modes = new SurfaceControl.DisplayMode[wrappedModes.length]; for (int i = 0; i < wrappedModes.length; i++) { modes[i] = wrappedModes[i].mode; } display.dynamicInfo.supportedDisplayModes = modes; setUpDisplay(display); mInjector.getTransmitter().sendHotplug(display, /* connected */ true); waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); assertTrue(mListener.traversalRequested); 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(modes.length); for (int i = 0; i < wrappedModes.length; i++) { assertModeIsSupported(supportedModes, modes[i], wrappedModes[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( createFakeDisplayMode(0, 1920, 1080, 60f, 0), new float[]{24f, 50f}), new DisplayModeWrapper( createFakeDisplayMode(1, 1920, 1080, 50f, 0), new float[]{24f, 60f}), new DisplayModeWrapper( createFakeDisplayMode(2, 1920, 1080, 24f, 0), new float[]{50f, 60f}), new DisplayModeWrapper( createFakeDisplayMode(3, 3840, 2160, 60f, 0), new float[]{24f, 50f}), new DisplayModeWrapper( createFakeDisplayMode(4, 3840, 2160, 50f, 0), new float[]{24f, 60f}), new DisplayModeWrapper( createFakeDisplayMode(5, 3840, 2160, 24f, 0), new float[]{50f, 60f}), }); testAlternativeRefreshRatesCommon(display, new DisplayModeWrapper[] { new DisplayModeWrapper( createFakeDisplayMode(0, 1920, 1080, 60f, 0), new float[]{50f}), new DisplayModeWrapper( createFakeDisplayMode(1, 1920, 1080, 50f, 0), new float[]{60f}), new DisplayModeWrapper( createFakeDisplayMode(2, 1920, 1080, 24f, 1), new float[0]), new DisplayModeWrapper( createFakeDisplayMode(3, 3840, 2160, 60f, 2), new float[0]), new DisplayModeWrapper( createFakeDisplayMode(4, 3840, 2160, 50f, 3), new float[]{24f}), new DisplayModeWrapper( createFakeDisplayMode(5, 3840, 2160, 24f, 3), new float[]{50f}), }); testAlternativeRefreshRatesCommon(display, new DisplayModeWrapper[] { new DisplayModeWrapper( createFakeDisplayMode(0, 1920, 1080, 60f, 0), new float[0]), new DisplayModeWrapper( createFakeDisplayMode(1, 1920, 1080, 50f, 1), new float[0]), new DisplayModeWrapper( createFakeDisplayMode(2, 1920, 1080, 24f, 2), new float[0]), new DisplayModeWrapper( createFakeDisplayMode(3, 3840, 2160, 60f, 3), new float[0]), new DisplayModeWrapper( createFakeDisplayMode(4, 3840, 2160, 50f, 4), new float[0]), new DisplayModeWrapper( createFakeDisplayMode(5, 3840, 2160, 24f, 5), new float[0]), }); } @Test public void testAfterDisplayChange_DefaultDisplayModeIsUpdated() throws Exception { 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); setUpDisplay(display); updateAvailableDisplays(); mAdapter.registerLocked(); waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); assertThat(mListener.addedDisplays.size()).isEqualTo(1); assertThat(mListener.changedDisplays).isEmpty(); DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get( 0).getDisplayDeviceInfoLocked(); assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(modes.length); assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode); Display.Mode defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId); assertThat(matches(defaultMode, displayMode)).isTrue(); // Set the display mode to an unsupported mode SurfaceControl.DisplayMode displayMode2 = createFakeDisplayMode(1, 1920, 1080, 120f); mListener.addedDisplays.get(0).setUserPreferredDisplayModeLocked( new Display.Mode(displayMode2.width, displayMode2.height, displayMode2.refreshRate)); updateAvailableDisplays(); waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId); assertThat(matches(defaultMode, displayMode2)).isFalse(); // Change the display modes = new SurfaceControl.DisplayMode[]{displayMode, displayMode2}; display.dynamicInfo.supportedDisplayModes = modes; 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 displayDevice = mListener.changedDisplays.get(0); displayDevice.applyPendingDisplayDeviceInfoChangesLocked(); displayDeviceInfo = mListener.addedDisplays.get(0).getDisplayDeviceInfoLocked(); assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(modes.length); assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode); assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode2); defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId); assertThat(matches(defaultMode, displayMode2)).isTrue(); } @Test public void testAfterDisplayChange_DisplayModesAreUpdated() throws Exception { 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); setUpDisplay(display); updateAvailableDisplays(); mAdapter.registerLocked(); waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); assertThat(mListener.addedDisplays.size()).isEqualTo(1); assertThat(mListener.changedDisplays).isEmpty(); DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get( 0).getDisplayDeviceInfoLocked(); assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(modes.length); assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode); Display.Mode defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId); assertThat(matches(defaultMode, displayMode)).isTrue(); Display.Mode activeMode = getModeById(displayDeviceInfo, displayDeviceInfo.modeId); assertThat(matches(activeMode, displayMode)).isTrue(); // Change the display SurfaceControl.DisplayMode addedDisplayInfo = createFakeDisplayMode(1, 3840, 2160, 60f); modes = new SurfaceControl.DisplayMode[]{displayMode, addedDisplayInfo}; display.dynamicInfo.supportedDisplayModes = modes; display.dynamicInfo.activeDisplayModeId = 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 displayDevice = mListener.changedDisplays.get(0); displayDevice.applyPendingDisplayDeviceInfoChangesLocked(); displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked(); assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(modes.length); assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode); assertModeIsSupported(displayDeviceInfo.supportedModes, addedDisplayInfo); activeMode = getModeById(displayDeviceInfo, displayDeviceInfo.modeId); assertThat(matches(activeMode, addedDisplayInfo)).isTrue(); defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId); assertThat(matches(defaultMode, addedDisplayInfo)).isTrue(); } @Test public void testAfterDisplayChange_ActiveModeIsUpdated() throws Exception { SurfaceControl.DisplayMode[] modes = new SurfaceControl.DisplayMode[]{ createFakeDisplayMode(0, 1920, 1080, 60f), createFakeDisplayMode(1, 1920, 1080, 50f) }; FakeDisplay display = new FakeDisplay(PORT_A, modes, /* activeMode */ 0, 60f); setUpDisplay(display); updateAvailableDisplays(); mAdapter.registerLocked(); waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); assertThat(mListener.addedDisplays.size()).isEqualTo(1); assertThat(mListener.changedDisplays).isEmpty(); DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(0) .getDisplayDeviceInfoLocked(); Display.Mode activeMode = getModeById(displayDeviceInfo, displayDeviceInfo.modeId); assertThat(activeMode.matches(1920, 1080, 60f)).isTrue(); // Change the display display.dynamicInfo.activeDisplayModeId = 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 displayDevice = mListener.changedDisplays.get(0); displayDevice.applyPendingDisplayDeviceInfoChangesLocked(); displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked(); activeMode = getModeById(displayDeviceInfo, displayDeviceInfo.modeId); assertThat(activeMode.matches(1920, 1080, 50f)).isTrue(); } @Test public void testAfterDisplayChange_RenderFrameRateIsUpdated() throws Exception { SurfaceControl.DisplayMode[] modes = new SurfaceControl.DisplayMode[]{ createFakeDisplayMode(0, 1920, 1080, 60f), }; FakeDisplay display = new FakeDisplay(PORT_A, modes, /* activeMode */ 0, /* renderFrameRate */30f); setUpDisplay(display); updateAvailableDisplays(); mAdapter.registerLocked(); waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); assertThat(mListener.addedDisplays.size()).isEqualTo(1); assertThat(mListener.changedDisplays).isEmpty(); DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(0) .getDisplayDeviceInfoLocked(); Display.Mode activeMode = getModeById(displayDeviceInfo, displayDeviceInfo.modeId); assertThat(activeMode.matches(1920, 1080, 60f)).isTrue(); assertEquals(Float.floatToIntBits(30f), Float.floatToIntBits(displayDeviceInfo.renderFrameRate)); // Change the render frame rate display.dynamicInfo.renderFrameRate = 60f; 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 displayDevice = mListener.changedDisplays.get(0); displayDevice.applyPendingDisplayDeviceInfoChangesLocked(); displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked(); activeMode = getModeById(displayDeviceInfo, displayDeviceInfo.modeId); assertThat(activeMode.matches(1920, 1080, 60f)).isTrue(); assertEquals(Float.floatToIntBits(60f), Float.floatToIntBits(displayDeviceInfo.renderFrameRate)); } @Test public void testAfterDisplayChange_HdrCapabilitiesAreUpdated() throws Exception { FakeDisplay display = new FakeDisplay(PORT_A); Display.HdrCapabilities initialHdrCapabilities = new Display.HdrCapabilities(new int[0], 1000, 1000, 0); display.dynamicInfo.hdrCapabilities = initialHdrCapabilities; setUpDisplay(display); updateAvailableDisplays(); mAdapter.registerLocked(); waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); assertThat(mListener.addedDisplays.size()).isEqualTo(1); assertThat(mListener.changedDisplays).isEmpty(); DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get( 0).getDisplayDeviceInfoLocked(); assertThat(displayDeviceInfo.hdrCapabilities).isEqualTo(initialHdrCapabilities); // Change the display Display.HdrCapabilities changedHdrCapabilities = new Display.HdrCapabilities( new int[Display.HdrCapabilities.HDR_TYPE_HDR10_PLUS], 1000, 1000, 0); display.dynamicInfo.hdrCapabilities = changedHdrCapabilities; 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 displayDevice = mListener.changedDisplays.get(0); displayDevice.applyPendingDisplayDeviceInfoChangesLocked(); displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked(); assertThat(displayDeviceInfo.hdrCapabilities).isEqualTo(changedHdrCapabilities); } @Test public void testAfterDisplayChange_AllmSupportIsUpdated() throws Exception { FakeDisplay display = new FakeDisplay(PORT_A); display.dynamicInfo.autoLowLatencyModeSupported = true; setUpDisplay(display); updateAvailableDisplays(); mAdapter.registerLocked(); waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); assertThat(mListener.addedDisplays.size()).isEqualTo(1); assertThat(mListener.changedDisplays).isEmpty(); DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(0) .getDisplayDeviceInfoLocked(); assertThat(displayDeviceInfo.allmSupported).isTrue(); // Change the display display.dynamicInfo.autoLowLatencyModeSupported = false; 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 displayDevice = mListener.changedDisplays.get(0); displayDevice.applyPendingDisplayDeviceInfoChangesLocked(); displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked(); assertThat(displayDeviceInfo.allmSupported).isFalse(); } @Test public void testAfterDisplayStateChanges_committedSetAfterState() throws Exception { FakeDisplay display = new FakeDisplay(PORT_A); setUpDisplay(display); updateAvailableDisplays(); mAdapter.registerLocked(); waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); assertThat(mListener.addedDisplays.size()).isEqualTo(1); DisplayDevice displayDevice = mListener.addedDisplays.get(0); // Turn off. Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_OFF, 0, 0); waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); assertThat(mListener.changedDisplays.size()).isEqualTo(1); mListener.changedDisplays.clear(); assertThat(displayDevice.getDisplayDeviceInfoLocked().state).isEqualTo(Display.STATE_OFF); assertThat(displayDevice.getDisplayDeviceInfoLocked().committedState).isNotEqualTo( Display.STATE_OFF); verify(mSurfaceControlProxy, never()).setDisplayPowerMode(display.token, Display.STATE_OFF); // Execute powerstate change. changeStateRunnable.run(); waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); // Verify that committed triggered a new change event and is set correctly. verify(mSurfaceControlProxy, never()).setDisplayPowerMode(display.token, Display.STATE_OFF); // We expect at least 1 update for the state change, but // could get a second update for the initial brightness change if a nits mapping // is available assertThat(mListener.changedDisplays.size()).isAnyOf(1, 2); assertThat(displayDevice.getDisplayDeviceInfoLocked().state).isEqualTo(Display.STATE_OFF); assertThat(displayDevice.getDisplayDeviceInfoLocked().committedState).isEqualTo( Display.STATE_OFF); } @Test public void testAfterDisplayChange_GameContentTypeSupportIsUpdated() throws Exception { FakeDisplay display = new FakeDisplay(PORT_A); display.dynamicInfo.gameContentTypeSupported = true; setUpDisplay(display); updateAvailableDisplays(); mAdapter.registerLocked(); waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); assertThat(mListener.addedDisplays.size()).isEqualTo(1); assertThat(mListener.changedDisplays).isEmpty(); DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(0) .getDisplayDeviceInfoLocked(); assertThat(displayDeviceInfo.gameContentTypeSupported).isTrue(); // Change the display display.dynamicInfo.gameContentTypeSupported = false; 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 displayDevice = mListener.changedDisplays.get(0); displayDevice.applyPendingDisplayDeviceInfoChangesLocked(); displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked(); assertThat(displayDeviceInfo.gameContentTypeSupported).isFalse(); } @Test public void testAfterDisplayChange_ColorModesAreUpdated() throws Exception { FakeDisplay display = new FakeDisplay(PORT_A); final int[] initialColorModes = new int[]{Display.COLOR_MODE_BT709}; display.dynamicInfo.supportedColorModes = initialColorModes; setUpDisplay(display); updateAvailableDisplays(); mAdapter.registerLocked(); waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); assertThat(mListener.addedDisplays.size()).isEqualTo(1); assertThat(mListener.changedDisplays).isEmpty(); DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(0) .getDisplayDeviceInfoLocked(); assertThat(displayDeviceInfo.colorMode).isEqualTo(Display.COLOR_MODE_BT709); assertThat(displayDeviceInfo.supportedColorModes).isEqualTo(initialColorModes); // Change the display final int[] changedColorModes = new int[]{Display.COLOR_MODE_DEFAULT}; display.dynamicInfo.supportedColorModes = changedColorModes; 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 displayDevice = mListener.changedDisplays.get(0); displayDevice.applyPendingDisplayDeviceInfoChangesLocked(); displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked(); assertThat(displayDeviceInfo.colorMode).isEqualTo(Display.COLOR_MODE_DEFAULT); assertThat(displayDeviceInfo.supportedColorModes).isEqualTo(changedColorModes); } @Test public void testDisplayChange_withStaleDesiredDisplayModeSpecs() throws Exception { SurfaceControl.DisplayMode[] modes = new SurfaceControl.DisplayMode[]{ createFakeDisplayMode(0, 1920, 1080, 60f), createFakeDisplayMode(1, 1920, 1080, 50f) }; final int activeMode = 0; FakeDisplay display = new FakeDisplay(PORT_A, modes, activeMode, 60f); display.desiredDisplayModeSpecs.defaultMode = 1; setUpDisplay(display); updateAvailableDisplays(); mAdapter.registerLocked(); waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); assertThat(mListener.addedDisplays.size()).isEqualTo(1); DisplayDevice displayDevice = mListener.addedDisplays.get(0); int baseModeId = Arrays.stream(displayDevice.getDisplayDeviceInfoLocked().supportedModes) .filter(mode -> mode.getRefreshRate() == 60f) .findFirst() .get() .getModeId(); displayDevice.setDesiredDisplayModeSpecsLocked( new DisplayModeDirector.DesiredDisplayModeSpecs( /*baseModeId*/ baseModeId, /*allowGroupSwitching*/ false, REFRESH_RATE_RANGES, REFRESH_RATE_RANGES )); waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); verify(mSurfaceControlProxy).setDesiredDisplayModeSpecs(display.token, new SurfaceControl.DesiredDisplayModeSpecs( /* baseModeId */ 0, /* allowGroupSwitching */ false, REFRESH_RATE_RANGES, REFRESH_RATE_RANGES )); // Change the display display.dynamicInfo.supportedDisplayModes = new SurfaceControl.DisplayMode[]{ createFakeDisplayMode(2, 1920, 1080, 60f) }; display.dynamicInfo.activeDisplayModeId = 2; // SurfaceFlinger can return a stale defaultMode. Make sure this doesn't crash. display.desiredDisplayModeSpecs.defaultMode = 1; setUpDisplay(display); mInjector.getTransmitter().sendHotplug(display, /* connected */ true); waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); assertTrue(mListener.traversalRequested); displayDevice.applyPendingDisplayDeviceInfoChangesLocked(); baseModeId = displayDevice.getDisplayDeviceInfoLocked().supportedModes[0].getModeId(); // The traversal request will call setDesiredDisplayModeSpecsLocked on the display device displayDevice.setDesiredDisplayModeSpecsLocked( new DisplayModeDirector.DesiredDisplayModeSpecs( /*baseModeId*/ baseModeId, /*allowGroupSwitching*/ false, REFRESH_RATE_RANGES, REFRESH_RATE_RANGES )); waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); // Verify that this will reapply the desired modes. verify(mSurfaceControlProxy).setDesiredDisplayModeSpecs(display.token, new SurfaceControl.DesiredDisplayModeSpecs( /* baseModeId */ 2, /* allowGroupSwitching */ false, REFRESH_RATE_RANGES, REFRESH_RATE_RANGES )); } @Test public void testBacklightAdapter_withSurfaceControlSupport() { final Binder displayToken = new Binder(); when(mSurfaceControlProxy.getDisplayBrightnessSupport(displayToken)).thenReturn(true); // Test as default display BacklightAdapter ba = new BacklightAdapter(displayToken, true /*isDefault*/, mSurfaceControlProxy); ba.setBacklight(0.514f, 100f, 0.614f, 500f); verify(mSurfaceControlProxy).setDisplayBrightness(displayToken, 0.514f, 100f, 0.614f, 500f); // Test as not default display BacklightAdapter ba2 = new BacklightAdapter(displayToken, false /*isDefault*/, mSurfaceControlProxy); ba2.setBacklight(0.323f, 101f, 0.723f, 601f); verify(mSurfaceControlProxy).setDisplayBrightness(displayToken, 0.323f, 101f, 0.723f, 601f); } @Test public void testBacklightAdapter_withoutSourceControlSupport_defaultDisplay() { final Binder displayToken = new Binder(); when(mSurfaceControlProxy.getDisplayBrightnessSupport(displayToken)).thenReturn(false); doReturn(mMockedBacklight).when(mMockedLightsManager) .getLight(LightsManager.LIGHT_ID_BACKLIGHT); BacklightAdapter ba = new BacklightAdapter(displayToken, true /*isDefault*/, mSurfaceControlProxy); ba.setBacklight(1f, 1f, 0.123f, 1f); verify(mMockedBacklight).setBrightness(0.123f); } @Test public void testBacklightAdapter_withoutSourceControlSupport_nonDefaultDisplay() { final Binder displayToken = new Binder(); when(mSurfaceControlProxy.getDisplayBrightnessSupport(displayToken)).thenReturn(false); doReturn(mMockedBacklight).when(mMockedLightsManager) .getLight(LightsManager.LIGHT_ID_BACKLIGHT); BacklightAdapter ba = new BacklightAdapter(displayToken, false /*isDefault*/, mSurfaceControlProxy); ba.setBacklight(0.456f, 1f, 1f, 1f); // Adapter does not forward any brightness in this case. verify(mMockedBacklight, never()).setBrightness(anyFloat()); } @Test public void testGetSystemPreferredDisplayMode() throws Exception { SurfaceControl.DisplayMode displayMode1 = createFakeDisplayMode(0, 1920, 1080, 60f); // system preferred mode SurfaceControl.DisplayMode displayMode2 = createFakeDisplayMode(1, 3840, 2160, 60f); // user preferred mode SurfaceControl.DisplayMode displayMode3 = createFakeDisplayMode(2, 1920, 1080, 30f); SurfaceControl.DisplayMode[] modes = new SurfaceControl.DisplayMode[]{displayMode1, displayMode2, displayMode3}; FakeDisplay display = new FakeDisplay(PORT_A, modes, 0, 1); setUpDisplay(display); updateAvailableDisplays(); mAdapter.registerLocked(); waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); assertThat(mListener.addedDisplays.size()).isEqualTo(1); assertThat(mListener.changedDisplays).isEmpty(); DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get( 0).getDisplayDeviceInfoLocked(); assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(modes.length); Display.Mode defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId); assertThat(matches(defaultMode, displayMode1)).isTrue(); // Set the user preferred display mode mListener.addedDisplays.get(0).setUserPreferredDisplayModeLocked( new Display.Mode( displayMode3.width, displayMode3.height, displayMode3.refreshRate)); updateAvailableDisplays(); waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); displayDeviceInfo = mListener.addedDisplays.get( 0).getDisplayDeviceInfoLocked(); defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId); assertThat(matches(defaultMode, displayMode3)).isTrue(); // clear the user preferred mode mListener.addedDisplays.get(0).setUserPreferredDisplayModeLocked(null); updateAvailableDisplays(); waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); displayDeviceInfo = mListener.addedDisplays.get( 0).getDisplayDeviceInfoLocked(); defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId); assertThat(matches(defaultMode, displayMode2)).isTrue(); // Change the display and add new system preferred mode SurfaceControl.DisplayMode addedDisplayInfo = createFakeDisplayMode(3, 2340, 1080, 20f); modes = new SurfaceControl.DisplayMode[]{ displayMode1, displayMode2, displayMode3, addedDisplayInfo}; display.dynamicInfo.supportedDisplayModes = modes; display.dynamicInfo.preferredBootDisplayMode = 3; 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(3); DisplayDevice displayDevice = mListener.changedDisplays.get(0); displayDevice.applyPendingDisplayDeviceInfoChangesLocked(); displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked(); assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(modes.length); assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode1); assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode2); assertModeIsSupported(displayDeviceInfo.supportedModes, addedDisplayInfo); assertThat(matches(displayDevice.getSystemPreferredDisplayModeLocked(), addedDisplayInfo)) .isTrue(); } @Test public void testHdrSdrRatio_notifiesOnChange() throws Exception { FakeDisplay display = new FakeDisplay(PORT_A); setUpDisplay(display); updateAvailableDisplays(); mAdapter.registerLocked(); waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); assertThat(mListener.addedDisplays.size()).isEqualTo(1); DisplayDevice displayDevice = mListener.addedDisplays.get(0); // Turn on / initialize assumeTrue(displayDevice.getDisplayDeviceConfig().hasSdrToHdrRatioSpline()); Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0, 0); changeStateRunnable.run(); waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); mListener.changedDisplays.clear(); assertEquals(1.0f, displayDevice.getDisplayDeviceInfoLocked().hdrSdrRatio, 0.001f); // HDR time! Runnable goHdrRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 1f, 0); waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); // Display state didn't change, no listeners should have happened assertThat(mListener.changedDisplays.size()).isEqualTo(0); // Execute hdr change. goHdrRunnable.run(); waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); // Display state didn't change, expect to only get the HDR/SDR ratio change notification assertThat(mListener.changedDisplays.size()).isEqualTo(1); final float expectedRatio = DISPLAY_RANGE_NITS[1] / DISPLAY_RANGE_NITS[0]; assertEquals(expectedRatio, displayDevice.getDisplayDeviceInfoLocked().hdrSdrRatio, 0.001f); } @Test public void test_getDisplayDeviceInfoLocked_internalDisplay_usesCutoutAndCorners() throws Exception { setupCutoutAndRoundedCorners(); FakeDisplay display = new FakeDisplay(PORT_A); display.info.isInternal = true; setUpDisplay(display); updateAvailableDisplays(); mAdapter.registerLocked(); waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); assertThat(mListener.addedDisplays.size()).isEqualTo(1); DisplayDevice displayDevice = mListener.addedDisplays.get(0); // Turn on / initialize Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0, 0); changeStateRunnable.run(); waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); mListener.changedDisplays.clear(); DisplayDeviceInfo info = displayDevice.getDisplayDeviceInfoLocked(); assertThat(info.displayCutout).isNotNull(); assertThat(info.displayCutout.getBoundingRectTop()).isEqualTo(new Rect(507, 33, 573, 99)); assertThat(info.roundedCorners).isNotNull(); assertThat(info.roundedCorners.getRoundedCorner(0).getRadius()).isEqualTo(5); } @Test public void test_getDisplayDeviceInfoLocked_externalDisplay_doesNotUseCutoutOrCorners() throws Exception { setupCutoutAndRoundedCorners(); FakeDisplay display = new FakeDisplay(PORT_A); display.info.isInternal = false; setUpDisplay(display); updateAvailableDisplays(); mAdapter.registerLocked(); waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); assertThat(mListener.addedDisplays.size()).isEqualTo(1); DisplayDevice displayDevice = mListener.addedDisplays.get(0); // Turn on / initialize Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0, 0); changeStateRunnable.run(); waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); mListener.changedDisplays.clear(); DisplayDeviceInfo info = displayDevice.getDisplayDeviceInfoLocked(); assertThat(info.displayCutout).isNull(); assertThat(info.roundedCorners).isNull(); } private void setupCutoutAndRoundedCorners() { String sampleCutout = "M 507,66\n" + "a 33,33 0 1 0 66,0 33,33 0 1 0 -66,0\n" + "Z\n" + "@left\n"; // Setup some default cutout when(mMockedResources.getString( com.android.internal.R.string.config_mainBuiltInDisplayCutout)) .thenReturn(sampleCutout); when(mMockedResources.getDimensionPixelSize( com.android.internal.R.dimen.rounded_corner_radius)).thenReturn(5); } private void assertDisplayDpi(DisplayDeviceInfo info, int expectedPort, float expectedXdpi, float expectedYDpi, int expectedDensityDpi) { final DisplayAddress.Physical physical = (DisplayAddress.Physical) info.address; assertNotNull(physical); assertEquals(expectedPort, physical.getPort()); assertEquals(expectedXdpi, info.xDpi, 0.01); assertEquals(expectedYDpi, info.yDpi, 0.01); assertEquals(expectedDensityDpi, info.densityDpi); } private Display.Mode getModeById(DisplayDeviceInfo displayDeviceInfo, int modeId) { return Arrays.stream(displayDeviceInfo.supportedModes) .filter(mode -> mode.getModeId() == modeId) .findFirst() .get(); } private void assertModeIsSupported(Display.Mode[] supportedModes, SurfaceControl.DisplayMode mode) { assertThat(Arrays.stream(supportedModes).anyMatch( x -> x.matches(mode.width, mode.height, mode.refreshRate))).isTrue(); } private void assertModeIsSupported(Display.Mode[] supportedModes, SurfaceControl.DisplayMode 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(); public final SurfaceControl.StaticDisplayInfo info; public SurfaceControl.DynamicDisplayInfo dynamicInfo = new SurfaceControl.DynamicDisplayInfo(); { dynamicInfo.supportedColorModes = new int[]{ Display.COLOR_MODE_DEFAULT }; dynamicInfo.hdrCapabilities = new Display.HdrCapabilities(new int[0], 1000, 1000, 0); } public SurfaceControl.DesiredDisplayModeSpecs desiredDisplayModeSpecs = new SurfaceControl.DesiredDisplayModeSpecs( /* defaultMode */ 0, /* allowGroupSwitching */ false, REFRESH_RATE_RANGES, REFRESH_RATE_RANGES ); private FakeDisplay(int port) { address = createDisplayAddress(port); info = createFakeDisplayInfo(); dynamicInfo.supportedDisplayModes = new SurfaceControl.DisplayMode[]{ createFakeDisplayMode(0, 800, 600, 60f) }; dynamicInfo.activeDisplayModeId = 0; } private FakeDisplay(int port, SurfaceControl.DisplayMode[] modes, int activeMode, float renderFrameRate) { address = createDisplayAddress(port); info = createFakeDisplayInfo(); dynamicInfo.supportedDisplayModes = modes; dynamicInfo.activeDisplayModeId = activeMode; dynamicInfo.renderFrameRate = renderFrameRate; } private FakeDisplay(int port, SurfaceControl.DisplayMode[] modes, int activeMode, int preferredMode) { address = createDisplayAddress(port); info = createFakeDisplayInfo(); dynamicInfo.supportedDisplayModes = modes; dynamicInfo.activeDisplayModeId = activeMode; dynamicInfo.preferredBootDisplayMode = preferredMode; } } private void setUpDisplay(FakeDisplay display) { mAddresses.add(display.address); when(mSurfaceControlProxy.getPhysicalDisplayToken(display.address.getPhysicalDisplayId())) .thenReturn(display.token); when(mSurfaceControlProxy.getStaticDisplayInfo(display.address.getPhysicalDisplayId())) .thenReturn(display.info); when(mSurfaceControlProxy.getDynamicDisplayInfo(display.address.getPhysicalDisplayId())) .thenReturn(display.dynamicInfo); when(mSurfaceControlProxy.getDesiredDisplayModeSpecs(display.token)) .thenReturn(display.desiredDisplayModeSpecs); } private void updateAvailableDisplays() { long[] ids = new long[mAddresses.size()]; int i = 0; for (DisplayAddress.Physical address : mAddresses) { ids[i] = address.getPhysicalDisplayId(); i++; } when(mSurfaceControlProxy.getPhysicalDisplayIds()).thenReturn(ids); } private static DisplayAddress.Physical createDisplayAddress(int port) { return DisplayAddress.fromPortAndModel(port, DISPLAY_MODEL); } private static SurfaceControl.StaticDisplayInfo createFakeDisplayInfo() { final SurfaceControl.StaticDisplayInfo info = new SurfaceControl.StaticDisplayInfo(); info.density = 100; return info; } private static SurfaceControl.DisplayMode createFakeDisplayMode(int id, int width, int height, float refreshRate) { return createFakeDisplayMode(id, width, height, refreshRate, /* group */ 0); } private static SurfaceControl.DisplayMode createFakeDisplayMode(int id, int width, int height, float refreshRate, int group) { final SurfaceControl.DisplayMode mode = new SurfaceControl.DisplayMode(); mode.id = id; mode.width = width; mode.height = height; mode.refreshRate = refreshRate; mode.xDpi = 100; mode.yDpi = 100; mode.group = group; mode.supportedHdrTypes = HDR_TYPES; return mode; } private static void waitForHandlerToComplete(Handler handler, long waitTimeMs) throws InterruptedException { final CountDownLatch fence = new CountDownLatch(1); handler.post(fence::countDown); assertTrue(fence.await(waitTimeMs, TimeUnit.MILLISECONDS)); } 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; } @Override public LocalDisplayAdapter.SurfaceControlProxy getSurfaceControlProxy() { return mSurfaceControlProxy; } // Instead of using DisplayDeviceConfig.create(context, physicalDisplayId, isFirstDisplay) // we should use DisplayDeviceConfig.create(context, isFirstDisplay) for the test to ensure // that real device DisplayDeviceConfig is not loaded for FakeDisplay and we are getting // consistent behaviour. Please also note that context passed to this method, is // mMockContext and values will be loaded from mMockResources. @Override public DisplayDeviceConfig createDisplayDeviceConfig(Context context, long physicalDisplayId, boolean isFirstDisplay) { return DisplayDeviceConfig.create(context, isFirstDisplay); } } private class TestListener implements DisplayAdapter.Listener { public ArrayList addedDisplays = new ArrayList<>(); public ArrayList changedDisplays = new ArrayList<>(); public boolean traversalRequested = false; @Override public void onDisplayDeviceEvent(DisplayDevice device, int event) { if (event == DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED) { addedDisplays.add(device); } else if (event == DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED) { changedDisplays.add(device); } } @Override public void onTraversalRequested() { traversalRequested = true; } } private TypedArray createFloatTypedArray(float[] vals) { TypedArray mockArray = mock(TypedArray.class); when(mockArray.length()).thenAnswer(invocation -> { return vals.length; }); when(mockArray.getFloat(anyInt(), anyFloat())).thenAnswer(invocation -> { final float def = (float) invocation.getArguments()[1]; if (vals == null) { return def; } int idx = (int) invocation.getArguments()[0]; if (idx >= 0 && idx < vals.length) { return vals[idx]; } else { return def; } }); return mockArray; } 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); } }