From d3fc996df76498472398f65596d36d3b020a5219 Mon Sep 17 00:00:00 2001 From: Fiona Campbell Date: Fri, 11 Nov 2022 17:24:07 +0000 Subject: [PATCH] Add position to displaylayoutconfig Position of each display in a state can be defined in display_layout_configuration.xml Bug: 259038047 Test: dumpsys display | grep addr Test: atest DeviceStateToLayoutMapTest Change-Id: Idac00886cd0a88d1db7d7401a707d7185a4eb8d8 --- .../display/DeviceStateToLayoutMap.java | 63 +++++--- .../server/display/LogicalDisplayMapper.java | 16 +- .../display/layout/DisplayIdProducer.java | 30 ++++ .../android/server/display/layout/Layout.java | 71 ++++++++- .../display-layout-config.xsd | 1 + .../display-layout-config/schema/current.txt | 2 + .../display/DeviceStateToLayoutMapTest.java | 150 ++++++++++++++++++ .../display/LogicalDisplayMapperTest.java | 38 +++-- 8 files changed, 332 insertions(+), 39 deletions(-) create mode 100644 services/core/java/com/android/server/display/layout/DisplayIdProducer.java create mode 100644 services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java diff --git a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java index 9dd2f8408c560..fc6403d2dfb7e 100644 --- a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java +++ b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java @@ -16,6 +16,7 @@ package com.android.server.display; +import android.annotation.NonNull; import android.hardware.devicestate.DeviceStateManager; import android.os.Environment; import android.util.IndentingPrintWriter; @@ -23,8 +24,10 @@ import android.util.Slog; import android.util.SparseArray; import android.view.DisplayAddress; +import com.android.internal.annotations.VisibleForTesting; import com.android.server.display.config.layout.Layouts; import com.android.server.display.config.layout.XmlParser; +import com.android.server.display.layout.DisplayIdProducer; import com.android.server.display.layout.Layout; import org.xmlpull.v1.XmlPullParserException; @@ -48,13 +51,28 @@ class DeviceStateToLayoutMap { public static final int STATE_DEFAULT = DeviceStateManager.INVALID_DEVICE_STATE; + // Direction of the display relative to the default display, whilst in this state + private static final int POSITION_UNKNOWN = Layout.Display.POSITION_UNKNOWN; + private static final int POSITION_FRONT = Layout.Display.POSITION_FRONT; + private static final int POSITION_REAR = Layout.Display.POSITION_REAR; + + private static final String FRONT_STRING = "front"; + private static final String REAR_STRING = "rear"; + private static final String CONFIG_FILE_PATH = "etc/displayconfig/display_layout_configuration.xml"; private final SparseArray mLayoutMap = new SparseArray<>(); + private final DisplayIdProducer mIdProducer; - DeviceStateToLayoutMap() { - loadLayoutsFromConfig(); + DeviceStateToLayoutMap(DisplayIdProducer idProducer) { + this(idProducer, Environment.buildPath( + Environment.getVendorDirectory(), CONFIG_FILE_PATH)); + } + + DeviceStateToLayoutMap(DisplayIdProducer idProducer, File configFile) { + mIdProducer = idProducer; + loadLayoutsFromConfig(configFile); createLayout(STATE_DEFAULT); } @@ -76,24 +94,11 @@ class DeviceStateToLayoutMap { return layout; } - private Layout createLayout(int state) { - if (mLayoutMap.contains(state)) { - Slog.e(TAG, "Attempted to create a second layout for state " + state); - return null; - } - - final Layout layout = new Layout(); - mLayoutMap.append(state, layout); - return layout; - } - /** * Reads display-layout-configuration files to get the layouts to use for this device. */ - private void loadLayoutsFromConfig() { - final File configFile = Environment.buildPath( - Environment.getVendorDirectory(), CONFIG_FILE_PATH); - + @VisibleForTesting + void loadLayoutsFromConfig(@NonNull File configFile) { if (!configFile.exists()) { return; } @@ -109,10 +114,19 @@ class DeviceStateToLayoutMap { final int state = l.getState().intValue(); final Layout layout = createLayout(state); for (com.android.server.display.config.layout.Display d: l.getDisplay()) { - layout.createDisplayLocked( + Layout.Display display = layout.createDisplayLocked( DisplayAddress.fromPhysicalDisplayId(d.getAddress().longValue()), d.isDefaultDisplay(), - d.isEnabled()); + d.isEnabled(), + mIdProducer); + + if (FRONT_STRING.equals(d.getPosition())) { + display.setPosition(POSITION_FRONT); + } else if (REAR_STRING.equals(d.getPosition())) { + display.setPosition(POSITION_REAR); + } else { + display.setPosition(POSITION_UNKNOWN); + } } } } catch (IOException | DatatypeConfigurationException | XmlPullParserException e) { @@ -120,4 +134,15 @@ class DeviceStateToLayoutMap { + configFile, e); } } + + private Layout createLayout(int state) { + if (mLayoutMap.contains(state)) { + Slog.e(TAG, "Attempted to create a second layout for state " + state); + return null; + } + + final Layout layout = new Layout(); + mLayoutMap.append(state, layout); + return layout; + } } diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java index 66073c2abdec8..80f47a138d08a 100644 --- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java +++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java @@ -40,6 +40,7 @@ import android.view.DisplayAddress; import android.view.DisplayInfo; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.display.layout.DisplayIdProducer; import com.android.server.display.layout.Layout; import java.io.PrintWriter; @@ -82,6 +83,8 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { private static final int UPDATE_STATE_TRANSITION = 1; private static final int UPDATE_STATE_UPDATED = 2; + private static int sNextNonDefaultDisplayId = DEFAULT_DISPLAY + 1; + /** * Temporary display info, used for comparing display configurations. */ @@ -170,6 +173,8 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { private final ArrayMap mVirtualDeviceDisplayMapping = new ArrayMap<>(); private int mNextNonDefaultGroupId = Display.DEFAULT_DISPLAY_GROUP + 1; + private final DisplayIdProducer mIdProducer = (isDefault) -> + isDefault ? DEFAULT_DISPLAY : sNextNonDefaultDisplayId++; private Layout mCurrentLayout = null; private int mDeviceState = DeviceStateManager.INVALID_DEVICE_STATE; private int mPendingDeviceState = DeviceStateManager.INVALID_DEVICE_STATE; @@ -179,7 +184,9 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { LogicalDisplayMapper(@NonNull Context context, @NonNull DisplayDeviceRepository repo, @NonNull Listener listener, @NonNull DisplayManagerService.SyncRoot syncRoot, @NonNull Handler handler) { - this(context, repo, listener, syncRoot, handler, new DeviceStateToLayoutMap()); + this(context, repo, listener, syncRoot, handler, + new DeviceStateToLayoutMap((isDefault) -> isDefault ? DEFAULT_DISPLAY + : sNextNonDefaultDisplayId++)); } LogicalDisplayMapper(@NonNull Context context, @NonNull DisplayDeviceRepository repo, @@ -588,7 +595,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { // Create a logical display for the new display device LogicalDisplay display = createNewLogicalDisplayLocked( - device, Layout.assignDisplayIdLocked(false /*isDefault*/)); + device, mIdProducer.getId(/* isDefault= */ false)); applyLayoutLocked(); updateLogicalDisplaysLocked(); @@ -621,7 +628,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { & DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY) != 0 && !nextDeviceInfo.address.equals(deviceInfo.address)) { layout.createDisplayLocked(nextDeviceInfo.address, - /* isDefault= */ true, /* isEnabled= */ true); + /* isDefault= */ true, /* isEnabled= */ true, mIdProducer); applyLayoutLocked(); return; } @@ -1036,7 +1043,8 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { return; } final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked(); - layout.createDisplayLocked(info.address, /* isDefault= */ true, /* isEnabled= */ true); + layout.createDisplayLocked(info.address, /* isDefault= */ true, /* isEnabled= */ true, + mIdProducer); } private int assignLayerStackLocked(int displayId) { diff --git a/services/core/java/com/android/server/display/layout/DisplayIdProducer.java b/services/core/java/com/android/server/display/layout/DisplayIdProducer.java new file mode 100644 index 0000000000000..3029757cb5624 --- /dev/null +++ b/services/core/java/com/android/server/display/layout/DisplayIdProducer.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2022 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.layout; + +/** + * Interface for producing logical display ids. + */ +public interface DisplayIdProducer { + + /** + * Generates a new display ID + * @param isDefault if requested display is the default display. + * @return the next unique logical display Id. + */ + int getId(boolean isDefault); +} diff --git a/services/core/java/com/android/server/display/layout/Layout.java b/services/core/java/com/android/server/display/layout/Layout.java index 7e16ea8b3ace2..4a466fdb90f78 100644 --- a/services/core/java/com/android/server/display/layout/Layout.java +++ b/services/core/java/com/android/server/display/layout/Layout.java @@ -50,15 +50,33 @@ public class Layout { return mDisplays.toString(); } + @Override + public boolean equals(Object obj) { + + if (!(obj instanceof Layout)) { + return false; + } + + Layout otherLayout = (Layout) obj; + return this.mDisplays.equals(otherLayout.mDisplays); + } + + @Override + public int hashCode() { + return mDisplays.hashCode(); + } + /** * Creates a simple 1:1 LogicalDisplay mapping for the specified DisplayDevice. * * @param address Address of the device. * @param isDefault Indicates if the device is meant to be the default display. + * @param isEnabled Indicates if this display is usable and can be switched on * @return The new layout. */ public Display createDisplayLocked( - @NonNull DisplayAddress address, boolean isDefault, boolean isEnabled) { + @NonNull DisplayAddress address, boolean isDefault, boolean isEnabled, + DisplayIdProducer idProducer) { if (contains(address)) { Slog.w(TAG, "Attempting to add second definition for display-device: " + address); return null; @@ -74,7 +92,7 @@ public class Layout { // Note that the logical display ID is saved into the layout, so when switching between // different layouts, a logical display can be destroyed and later recreated with the // same logical display ID. - final int logicalDisplayId = assignDisplayIdLocked(isDefault); + final int logicalDisplayId = idProducer.getId(isDefault); final Display display = new Display(address, logicalDisplayId, isEnabled); mDisplays.add(display); @@ -158,25 +176,64 @@ public class Layout { * Describes how a {@link LogicalDisplay} is built from {@link DisplayDevice}s. */ public static class Display { + public static final int POSITION_UNKNOWN = -1; + public static final int POSITION_FRONT = 0; + public static final int POSITION_REAR = 1; + // Address of the display device to map to this display. private final DisplayAddress mAddress; // Logical Display ID to apply to this display. private final int mLogicalDisplayId; - // Indicates that this display is not usable and should remain off. + // Indicates if this display is usable and can be switched on private final boolean mIsEnabled; + // The direction the display faces + // {@link DeviceStateToLayoutMap.POSITION_FRONT} or + // {@link DeviceStateToLayoutMap.POSITION_REAR}. + // {@link DeviceStateToLayoutMap.POSITION_UNKNOWN} is unspecified. + private int mPosition; + Display(@NonNull DisplayAddress address, int logicalDisplayId, boolean isEnabled) { mAddress = address; mLogicalDisplayId = logicalDisplayId; mIsEnabled = isEnabled; + mPosition = POSITION_UNKNOWN; } @Override public String toString() { - return "{addr: " + mAddress + ", dispId: " + mLogicalDisplayId - + "(" + (mIsEnabled ? "ON" : "OFF") + ")}"; + return "{" + + "dispId: " + mLogicalDisplayId + + "(" + (mIsEnabled ? "ON" : "OFF") + ")" + + ", addr: " + mAddress + + ((mPosition == POSITION_UNKNOWN) ? "" : ", position: " + mPosition) + + "}"; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Display)) { + return false; + } + + Display otherDisplay = (Display) obj; + + return otherDisplay.mIsEnabled == this.mIsEnabled + && otherDisplay.mPosition == this.mPosition + && otherDisplay.mLogicalDisplayId == this.mLogicalDisplayId + && this.mAddress.equals(otherDisplay.mAddress); + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Boolean.hashCode(mIsEnabled); + result = 31 * result + mPosition; + result = 31 * result + mLogicalDisplayId; + result = 31 * result + mAddress.hashCode(); + return result; } public DisplayAddress getAddress() { @@ -190,5 +247,9 @@ public class Layout { public boolean isEnabled() { return mIsEnabled; } + + public void setPosition(int position) { + mPosition = position; + } } } diff --git a/services/core/xsd/display-layout-config/display-layout-config.xsd b/services/core/xsd/display-layout-config/display-layout-config.xsd index e14139a0860aa..842d97acf1d4f 100644 --- a/services/core/xsd/display-layout-config/display-layout-config.xsd +++ b/services/core/xsd/display-layout-config/display-layout-config.xsd @@ -50,6 +50,7 @@ + diff --git a/services/core/xsd/display-layout-config/schema/current.txt b/services/core/xsd/display-layout-config/schema/current.txt index f3915754a1a47..55f866cbc478a 100644 --- a/services/core/xsd/display-layout-config/schema/current.txt +++ b/services/core/xsd/display-layout-config/schema/current.txt @@ -4,11 +4,13 @@ package com.android.server.display.config.layout { public class Display { ctor public Display(); method public java.math.BigInteger getAddress(); + method public String getPosition(); method public boolean isDefaultDisplay(); method public boolean isEnabled(); method public void setAddress(java.math.BigInteger); method public void setDefaultDisplay(boolean); method public void setEnabled(boolean); + method public void setPosition(String); } public class Layout { diff --git a/services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java b/services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java new file mode 100644 index 0000000000000..bcae50e1e53b3 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2022 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 org.junit.Assert.assertEquals; + +import android.view.DisplayAddress; + +import androidx.test.filters.SmallTest; + +import com.android.server.display.layout.DisplayIdProducer; +import com.android.server.display.layout.Layout; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; + + +@SmallTest +public class DeviceStateToLayoutMapTest { + private DeviceStateToLayoutMap mDeviceStateToLayoutMap; + + @Mock DisplayIdProducer mDisplayIdProducerMock; + + @Before + public void setUp() throws IOException { + MockitoAnnotations.initMocks(this); + + Mockito.when(mDisplayIdProducerMock.getId(false)).thenReturn(1); + + setupDeviceStateToLayoutMap(); + } + + ////////////////// + // Test Methods // + ////////////////// + + @Test + public void testInitialState() { + Layout configLayout = mDeviceStateToLayoutMap.get(0); + + Layout testLayout = new Layout(); + testLayout.createDisplayLocked( + DisplayAddress.fromPhysicalDisplayId(123456L), /* isDefault= */ true, + /* isEnabled= */ true, mDisplayIdProducerMock); + testLayout.createDisplayLocked( + DisplayAddress.fromPhysicalDisplayId(78910L), /* isDefault= */ false, + /* isEnabled= */ false, mDisplayIdProducerMock); + assertEquals(testLayout, configLayout); + } + + @Test + public void testSwitchedState() { + Layout configLayout = mDeviceStateToLayoutMap.get(1); + + Layout testLayout = new Layout(); + testLayout.createDisplayLocked( + DisplayAddress.fromPhysicalDisplayId(78910L), /* isDefault= */ true, + /* isEnabled= */ true, mDisplayIdProducerMock); + testLayout.createDisplayLocked( + DisplayAddress.fromPhysicalDisplayId(123456L), /* isDefault= */ false, + /* isEnabled= */ false, mDisplayIdProducerMock); + + assertEquals(testLayout, configLayout); + } + + @Test + public void testConcurrentState() { + Layout configLayout = mDeviceStateToLayoutMap.get(2); + + Layout testLayout = new Layout(); + testLayout.createDisplayLocked( + DisplayAddress.fromPhysicalDisplayId(345L), /* isDefault= */ true, + /* isEnabled= */ true, mDisplayIdProducerMock); + testLayout.createDisplayLocked( + DisplayAddress.fromPhysicalDisplayId(678L), /* isDefault= */ false, + /* isEnabled= */ true, mDisplayIdProducerMock); + + assertEquals(testLayout, configLayout); + } + + //////////////////// + // Helper Methods // + //////////////////// + + private void setupDeviceStateToLayoutMap() throws IOException { + Path tempFile = Files.createTempFile("device_state_layout_map", ".tmp"); + Files.write(tempFile, getContent().getBytes(StandardCharsets.UTF_8)); + mDeviceStateToLayoutMap = new DeviceStateToLayoutMap(mDisplayIdProducerMock, + tempFile.toFile()); + } + + private String getContent() { + return "\n" + + "\n" + + "\n" + + "0 \n" + + "\n" + + "
123456
\n" + + "
\n" + + "\n" + + "
78910
\n" + + "
\n" + + "
\n" + + + "\n" + + "1 \n" + + "\n" + + "
78910
\n" + + "
\n" + + "\n" + + "
123456
\n" + + "
\n" + + "
\n" + + + "\n" + + "2 \n" + + "\n" + + "
345
\n" + + "
\n" + + "\n" + + "
678
\n" + + "
\n" + + "
\n" + + "
\n"; + } +} + diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java index 246945c2d968c..c7caa4372b0e4 100644 --- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java +++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java @@ -54,6 +54,7 @@ import android.view.DisplayInfo; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.server.display.layout.DisplayIdProducer; import com.android.server.display.layout.Layout; import org.junit.Before; @@ -76,6 +77,7 @@ public class LogicalDisplayMapperTest { private static int sUniqueTestDisplayId = 0; private static final int DEVICE_STATE_CLOSED = 0; private static final int DEVICE_STATE_OPEN = 2; + private static int sNextNonDefaultDisplayId = DEFAULT_DISPLAY + 1; private DisplayDeviceRepository mDisplayDeviceRepo; private LogicalDisplayMapper mLogicalDisplayMapper; @@ -83,12 +85,16 @@ public class LogicalDisplayMapperTest { private Handler mHandler; private PowerManager mPowerManager; + private final DisplayIdProducer mIdProducer = (isDefault) -> + isDefault ? DEFAULT_DISPLAY : sNextNonDefaultDisplayId++; + @Mock LogicalDisplayMapper.Listener mListenerMock; @Mock Context mContextMock; @Mock Resources mResourcesMock; @Mock IPowerManager mIPowerManagerMock; @Mock IThermalService mIThermalServiceMock; - @Spy DeviceStateToLayoutMap mDeviceStateToLayoutMapSpy = new DeviceStateToLayoutMap(); + @Spy DeviceStateToLayoutMap mDeviceStateToLayoutMapSpy = + new DeviceStateToLayoutMap(mIdProducer); @Captor ArgumentCaptor mDisplayCaptor; @@ -519,13 +525,17 @@ public class LogicalDisplayMapperTest { DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY); Layout layout = new Layout(); - layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address, true, true); - layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address, false, false); + layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address, + true, true, mIdProducer); + layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address, + false, false, mIdProducer); when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(layout); layout = new Layout(); - layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address, false, false); - layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address, true, true); + layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address, + false, false, mIdProducer); + layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address, + true, true, mIdProducer); when(mDeviceStateToLayoutMapSpy.get(1)).thenReturn(layout); when(mDeviceStateToLayoutMapSpy.get(2)).thenReturn(layout); @@ -580,15 +590,18 @@ public class LogicalDisplayMapperTest { threeDevicesEnabledLayout.createDisplayLocked( displayAddressOne, /* isDefault= */ true, - /* isEnabled= */ true); + /* isEnabled= */ true, + mIdProducer); threeDevicesEnabledLayout.createDisplayLocked( displayAddressTwo, /* isDefault= */ false, - /* isEnabled= */ true); + /* isEnabled= */ true, + mIdProducer); threeDevicesEnabledLayout.createDisplayLocked( displayAddressThree, /* isDefault= */ false, - /* isEnabled= */ true); + /* isEnabled= */ true, + mIdProducer); when(mDeviceStateToLayoutMapSpy.get(DeviceStateToLayoutMap.STATE_DEFAULT)) .thenReturn(threeDevicesEnabledLayout); @@ -622,15 +635,18 @@ public class LogicalDisplayMapperTest { oneDeviceEnabledLayout.createDisplayLocked( displayAddressOne, /* isDefault= */ true, - /* isEnabled= */ true); + /* isEnabled= */ true, + mIdProducer); oneDeviceEnabledLayout.createDisplayLocked( displayAddressTwo, /* isDefault= */ false, - /* isEnabled= */ false); + /* isEnabled= */ false, + mIdProducer); oneDeviceEnabledLayout.createDisplayLocked( displayAddressThree, /* isDefault= */ false, - /* isEnabled= */ false); + /* isEnabled= */ false, + mIdProducer); when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(oneDeviceEnabledLayout); when(mDeviceStateToLayoutMapSpy.get(1)).thenReturn(threeDevicesEnabledLayout);