blob: 8270845657c64a4277e08b4694ef60e4c344f0e4 [file] [log] [blame]
/*
* 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.assertFalse;
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.spy;
import static org.mockito.Mockito.times;
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.hardware.display.DisplayManagerInternal.DisplayOffloader;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.PowerManager;
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.feature.DisplayManagerFlags;
import com.android.server.display.mode.DisplayModeDirector;
import com.android.server.display.notifications.DisplayNotificationManager;
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.List;
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;
@Mock
private DisplayNotificationManager mMockedDisplayNotificationManager;
@Mock
private DisplayManagerFlags mFlags;
@Mock
private DisplayPowerControllerInterface mMockedDisplayPowerController;
private Handler mHandler;
private DisplayOffloadSessionImpl mDisplayOffloadSession;
private DisplayOffloader mDisplayOffloader;
private TestListener mListener = new TestListener();
private LinkedList<DisplayAddress.Physical> 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 };
private static final List<Integer> mDisplayOffloadSupportedStates
= new ArrayList<>(List.of(Display.STATE_DOZE_SUSPEND));
@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, mFlags, mMockedDisplayNotificationManager, 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[]{});
doReturn(true).when(mFlags).isDisplayOffloadEnabled();
initDisplayOffloadSession();
}
@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 <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[] 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.peakRefreshRate);
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.peakRefreshRate));
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.peakRefreshRate);
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, null);
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.peakRefreshRate));
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 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);
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, null);
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, null);
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, null);
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, null);
changeStateRunnable.run();
waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
mListener.changedDisplays.clear();
DisplayDeviceInfo info = displayDevice.getDisplayDeviceInfoLocked();
assertThat(info.displayCutout).isNull();
assertThat(info.roundedCorners).isNull();
}
@Test
public void test_createLocalExternalDisplay_displayManagementEnabled_shouldHaveDefaultGroup()
throws Exception {
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, null);
changeStateRunnable.run();
waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
mListener.changedDisplays.clear();
DisplayDeviceInfo info = displayDevice.getDisplayDeviceInfoLocked();
assertThat(info.flags & DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP).isEqualTo(0);
}
@Test
public void test_createLocalExternalDisplay_displayManagementDisabled_shouldNotHaveOwnGroup()
throws Exception {
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, null);
changeStateRunnable.run();
waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
mListener.changedDisplays.clear();
DisplayDeviceInfo info = displayDevice.getDisplayDeviceInfoLocked();
assertThat(info.flags & DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP).isEqualTo(0);
}
@Test
public void test_displayStateToSupportedState_DisplayOffloadStart()
throws InterruptedException {
// prepare a display.
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);
for (Integer supportedState : mDisplayOffloadSupportedStates) {
Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(
supportedState, 0, 0, mDisplayOffloadSession);
changeStateRunnable.run();
}
verify(mDisplayOffloader, times(mDisplayOffloadSupportedStates.size())).startOffload();
assertTrue(mDisplayOffloadSession.isActive());
}
@Test
public void test_displayStateToDozeFromDozeSuspend_DisplayOffloadStop()
throws InterruptedException {
// prepare a display.
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);
Runnable changeStateToDozeSuspendRunnable = displayDevice.requestDisplayStateLocked(
Display.STATE_DOZE_SUSPEND, 0, 0, mDisplayOffloadSession);
Runnable changeStateToDozeRunnable = displayDevice.requestDisplayStateLocked(
Display.STATE_DOZE, 0, 0, mDisplayOffloadSession);
changeStateToDozeSuspendRunnable.run();
changeStateToDozeRunnable.run();
verify(mDisplayOffloader).stopOffload();
assertFalse(mDisplayOffloadSession.isActive());
verify(mMockedDisplayPowerController).setBrightnessFromOffload(
PowerManager.BRIGHTNESS_INVALID_FLOAT);
}
private void initDisplayOffloadSession() {
mDisplayOffloader = spy(new DisplayOffloader() {
@Override
public boolean startOffload() {
return true;
}
@Override
public void stopOffload() {}
});
mDisplayOffloadSession = new DisplayOffloadSessionImpl(mDisplayOffloader,
mMockedDisplayPowerController);
}
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.peakRefreshRate))).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.peakRefreshRate)
&& 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, 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, float vsyncRate, int group) {
final SurfaceControl.DisplayMode mode = new SurfaceControl.DisplayMode();
mode.id = id;
mode.width = width;
mode.height = height;
mode.peakRefreshRate = refreshRate;
mode.vsyncRate = vsyncRate;
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<DisplayDevice> addedDisplays = new ArrayList<>();
public ArrayList<DisplayDevice> 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.peakRefreshRate)
&& Float.floatToIntBits(a.getVsyncRate())
== Float.floatToIntBits(b.vsyncRate);
}
}