Merge "Add position to displaylayoutconfig"

This commit is contained in:
Fiona Campbell
2022-12-06 10:34:49 +00:00
committed by Android (Google) Code Review
8 changed files with 332 additions and 39 deletions

View File

@@ -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<Layout> 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;
}
}

View File

@@ -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<String, Integer> 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) {

View File

@@ -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);
}

View File

@@ -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;
}
}
}

View File

@@ -50,6 +50,7 @@
<xs:complexType name="display">
<xs:sequence>
<xs:element name="address" type="xs:nonNegativeInteger"/>
<xs:element name="position" type="xs:string" minOccurs="0" maxOccurs="1" />
</xs:sequence>
<xs:attribute name="enabled" type="xs:boolean" use="optional" />
<xs:attribute name="defaultDisplay" type="xs:boolean" use="optional" />

View File

@@ -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 {

View File

@@ -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 "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+ "<layouts>\n"
+ "<layout>\n"
+ "<state>0</state> \n"
+ "<display enabled=\"true\" defaultDisplay=\"true\">\n"
+ "<address>123456</address>\n"
+ "</display>\n"
+ "<display enabled=\"false\">\n"
+ "<address>78910</address>\n"
+ "</display>\n"
+ "</layout>\n"
+ "<layout>\n"
+ "<state>1</state> \n"
+ "<display enabled=\"true\" defaultDisplay=\"true\">\n"
+ "<address>78910</address>\n"
+ "</display>\n"
+ "<display enabled=\"false\">\n"
+ "<address>123456</address>\n"
+ "</display>\n"
+ "</layout>\n"
+ "<layout>\n"
+ "<state>2</state> \n"
+ "<display enabled=\"true\" defaultDisplay=\"true\">\n"
+ "<address>345</address>\n"
+ "</display>\n"
+ "<display enabled=\"true\">\n"
+ "<address>678</address>\n"
+ "</display>\n"
+ "</layout>\n"
+ "</layouts>\n";
}
}

View File

@@ -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<LogicalDisplay> 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);