Add static display layout XML files.

Adds the ability to specify an XML configuration file to determine
how displays are laid out for specific device states.

Bug: 168208162
Bug: 170498827
Test: atest com.android.server.display
Change-Id: I488367ecca7a36e667b10a3b70eca3647d40455c
This commit is contained in:
Santos Cordon
2021-02-17 12:49:19 +00:00
parent b375855856
commit 6c1dca2e25
16 changed files with 198 additions and 116 deletions

View File

@@ -673,15 +673,6 @@
-->
</integer-array>
<!-- The device states (supplied by DeviceStateManager) that should be treated as unfolded by
the display fold controller. Default is empty. -->
<integer-array name="config_unfoldedDeviceStates">
<!-- Example:
<item>3</item>
<item>4</item>
-->
</integer-array>
<!-- Indicate the display area rect for foldable devices in folded state. -->
<string name="config_foldedArea"></string>
@@ -4675,15 +4666,6 @@
<!-- WindowsManager JetPack display features -->
<string name="config_display_features" translatable="false" />
<!-- Physical Display IDs of the display-devices that are swapped when a folding device folds.
This list is expected to contain two elements: the first is the display to use
when the device is folded, the second is the display to use when unfolded. If the array
is empty or the display IDs are not recognized, this feature is turned off and the value
ignored.
TODO: b/170470621 - remove once we can have multiple Internal displays in DMS as
well as a notification from DisplayStateManager. -->
<string-array name="config_internalFoldedPhysicalDisplayIds" translatable="false" />
<!-- Aspect ratio of task level letterboxing. Values <= 1.0 will be ignored.
Note: Activity min/max aspect ratio restrictions will still be respected by the
activity-level letterboxing (size-compat mode). Therefore this override can control the

View File

@@ -3769,7 +3769,6 @@
<!-- For Foldables -->
<java-symbol type="array" name="config_foldedDeviceStates" />
<java-symbol type="array" name="config_unfoldedDeviceStates" />
<java-symbol type="string" name="config_foldedArea" />
<java-symbol type="array" name="config_disableApksUnlessMatchedSku_apk_list" />
@@ -4163,7 +4162,6 @@
<java-symbol type="dimen" name="default_background_blur_radius" />
<java-symbol type="array" name="config_keep_warming_services" />
<java-symbol type="string" name="config_display_features" />
<java-symbol type="array" name="config_internalFoldedPhysicalDisplayIds" />
<java-symbol type="dimen" name="controls_thumbnail_image_max_height" />
<java-symbol type="dimen" name="controls_thumbnail_image_max_width" />

View File

@@ -97,6 +97,7 @@ java_library_static {
":platform-compat-config",
":platform-compat-overrides",
":display-device-config",
":display-layout-config",
":cec-config",
":device-state-config",
"java/com/android/server/EventLogTags.logtags",

View File

@@ -16,17 +16,26 @@
package com.android.server.display;
import android.content.Context;
import android.hardware.devicestate.DeviceStateManager;
import android.text.TextUtils;
import android.os.Environment;
import android.util.IndentingPrintWriter;
import android.util.Slog;
import android.util.SparseArray;
import android.view.DisplayAddress;
import com.android.server.display.config.layout.Layouts;
import com.android.server.display.config.layout.XmlParser;
import com.android.server.display.layout.Layout;
import java.util.Arrays;
import org.xmlpull.v1.XmlPullParserException;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import javax.xml.datatype.DatatypeConfigurationException;
/**
* Mapping from device states into {@link Layout}s. This allows us to map device
@@ -39,11 +48,14 @@ class DeviceStateToLayoutMap {
public static final int STATE_DEFAULT = DeviceStateManager.INVALID_DEVICE_STATE;
private static final String CONFIG_FILE_PATH =
"etc/displayconfig/display_layout_configuration.xml";
private final SparseArray<Layout> mLayoutMap = new SparseArray<>();
DeviceStateToLayoutMap(Context context) {
mLayoutMap.append(STATE_DEFAULT, new Layout());
loadFoldedDisplayConfig(context);
DeviceStateToLayoutMap() {
loadLayoutsFromConfig();
createLayout(STATE_DEFAULT);
}
public void dumpLocked(IndentingPrintWriter ipw) {
@@ -76,48 +88,36 @@ class DeviceStateToLayoutMap {
}
/**
* Loads config.xml-specified folded configurations for foldable devices.
* Reads display-layout-configuration files to get the layouts to use for this device.
*/
private void loadFoldedDisplayConfig(Context context) {
final String[] strDisplayIds = context.getResources().getStringArray(
com.android.internal.R.array.config_internalFoldedPhysicalDisplayIds);
if (strDisplayIds.length != 2 || TextUtils.isEmpty(strDisplayIds[0])
|| TextUtils.isEmpty(strDisplayIds[1])) {
Slog.w(TAG, "Folded display configuration invalid: [" + Arrays.toString(strDisplayIds)
+ "]");
private void loadLayoutsFromConfig() {
final File configFile = Environment.buildPath(
Environment.getVendorDirectory(), CONFIG_FILE_PATH);
if (!configFile.exists()) {
return;
}
final long[] displayIds;
try {
displayIds = new long[] {
Long.parseLong(strDisplayIds[0]),
Long.parseLong(strDisplayIds[1])
};
} catch (NumberFormatException nfe) {
Slog.w(TAG, "Folded display config non numerical: " + Arrays.toString(strDisplayIds));
return;
}
final int[] foldedDeviceStates = context.getResources().getIntArray(
com.android.internal.R.array.config_foldedDeviceStates);
final int[] unfoldedDeviceStates = context.getResources().getIntArray(
com.android.internal.R.array.config_unfoldedDeviceStates);
// Only add folded states if folded state config is not empty
if (foldedDeviceStates.length == 0 || unfoldedDeviceStates.length == 0) {
return;
}
for (int state : foldedDeviceStates) {
// Create the folded state layout
createLayout(state).createDisplayLocked(
DisplayAddress.fromPhysicalDisplayId(displayIds[0]), true /*isDefault*/);
}
for (int state : unfoldedDeviceStates) {
// Create the unfolded state layout
createLayout(state).createDisplayLocked(
DisplayAddress.fromPhysicalDisplayId(displayIds[1]), true /*isDefault*/);
Slog.i(TAG, "Loading display layouts from " + configFile);
try (InputStream in = new BufferedInputStream(new FileInputStream(configFile))) {
final Layouts layouts = XmlParser.read(in);
if (layouts == null) {
Slog.i(TAG, "Display layout config not found: " + configFile);
return;
}
for (com.android.server.display.config.layout.Layout l : layouts.getLayout()) {
final int state = l.getState().intValue();
final Layout layout = createLayout(state);
for (com.android.server.display.config.layout.Display d: l.getDisplay()) {
layout.createDisplayLocked(
DisplayAddress.fromPhysicalDisplayId(d.getAddress().longValue()),
d.getIsDefault(),
d.getEnabled());
}
}
} catch (IOException | DatatypeConfigurationException | XmlPullParserException e) {
Slog.e(TAG, "Encountered an error while reading/parsing display layout config file: "
+ configFile, e);
}
}
}

View File

@@ -423,7 +423,7 @@ public final class DisplayManagerService extends SystemService {
mHandler = new DisplayManagerHandler(DisplayThread.get().getLooper());
mUiHandler = UiThread.getHandler();
mDisplayDeviceRepo = new DisplayDeviceRepository(mSyncRoot, mPersistentDataStore);
mLogicalDisplayMapper = new LogicalDisplayMapper(context, mDisplayDeviceRepo,
mLogicalDisplayMapper = new LogicalDisplayMapper(mDisplayDeviceRepo,
new LogicalDisplayListener());
mDisplayModeDirector = new DisplayModeDirector(context, mHandler);
mBrightnessSynchronizer = new BrightnessSynchronizer(mContext);
@@ -1178,7 +1178,10 @@ public final class DisplayManagerService extends SystemService {
private void handleLogicalDisplayRemovedLocked(@NonNull LogicalDisplay display) {
final int displayId = display.getDisplayIdLocked();
mDisplayPowerControllers.removeReturnOld(displayId).stop();
final DisplayPowerController dpc = mDisplayPowerControllers.removeReturnOld(displayId);
if (dpc != null) {
dpc.stop();
}
mDisplayStates.delete(displayId);
mDisplayBrightnesses.delete(displayId);
DisplayManagerGlobal.invalidateLocalDisplayInfoCaches();
@@ -1200,9 +1203,6 @@ public final class DisplayManagerService extends SystemService {
// by the display power controller (if known).
DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
if ((info.flags & DisplayDeviceInfo.FLAG_NEVER_BLANK) == 0) {
// TODO - b/170498827 The rules regarding what display state to apply to each
// display will depend on the configuration/mapping of logical displays.
// Clean up LogicalDisplay.isEnabled() mechanism once this is fixed.
final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(device);
final int state;
final int displayId = display.getDisplayIdLocked();

View File

@@ -16,7 +16,6 @@
package com.android.server.display;
import android.content.Context;
import android.hardware.devicestate.DeviceStateManager;
import android.os.SystemProperties;
import android.text.TextUtils;
@@ -90,7 +89,6 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
private final DisplayDeviceRepository mDisplayDeviceRepo;
private final DeviceStateToLayoutMap mDeviceStateToLayoutMap;
private final Listener mListener;
private final int[] mFoldedDeviceStates;
/**
* Has an entry for every logical display that the rest of the system has been notified about.
@@ -122,16 +120,12 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
private Layout mCurrentLayout = null;
private int mDeviceState = DeviceStateManager.INVALID_DEVICE_STATE;
LogicalDisplayMapper(Context context, DisplayDeviceRepository repo, Listener listener) {
LogicalDisplayMapper(DisplayDeviceRepository repo, Listener listener) {
mDisplayDeviceRepo = repo;
mListener = listener;
mSingleDisplayDemoMode = SystemProperties.getBoolean("persist.demo.singledisplay", false);
mDisplayDeviceRepo.addListener(this);
mFoldedDeviceStates = context.getResources().getIntArray(
com.android.internal.R.array.config_foldedDeviceStates);
mDeviceStateToLayoutMap = new DeviceStateToLayoutMap(context);
mDeviceStateToLayoutMap = new DeviceStateToLayoutMap();
}
@Override
@@ -470,10 +464,10 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
}
/**
* Resets the current layout in preparation for a new layout; essentially just marks
* all the currently layed out displays as disabled. This ensures the display devices
* are turned off. If they are meant to be used in the new layout,
* {@link #applyLayoutLocked()} will reenabled them.
* Resets the current layout in preparation for a new layout. Layouts can specify if some
* displays should be disabled (OFF). When switching from one layout to another, we go
* through each of the displays and make sure any displays we might have disabled are
* enabled again.
*/
private void resetLayoutLocked() {
final Layout layout = mDeviceStateToLayoutMap.get(mDeviceState);
@@ -481,7 +475,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
final Layout.Display displayLayout = layout.getAt(i);
final LogicalDisplay display = getDisplayLocked(displayLayout.getLogicalDisplayId());
if (display != null) {
enableDisplayLocked(display, false);
enableDisplayLocked(display, true); // Reset all displays back to enabled
}
}
}
@@ -503,7 +497,8 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
// If the underlying display-device we want to use for this display
// doesn't exist, then skip it. This can happen at startup as display-devices
// trickle in one at a time, or if the layout has an error.
// trickle in one at a time. When the new display finally shows up, the layout is
// recalculated so that the display is properly added to the current layout.
final DisplayAddress address = displayLayout.getAddress();
final DisplayDevice device = mDisplayDeviceRepo.getByAddressLocked(address);
if (device == null) {
@@ -526,7 +521,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
if (newDisplay != oldDisplay) {
newDisplay.swapDisplaysLocked(oldDisplay);
}
enableDisplayLocked(newDisplay, true);
enableDisplayLocked(newDisplay, displayLayout.isEnabled());
}
}
@@ -545,13 +540,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
final LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device);
display.updateLocked(mDisplayDeviceRepo);
mLogicalDisplays.put(displayId, display);
// Internal displays start off disabled. The display is enabled later if it is part of the
// currently selected display layout.
final boolean isEnabled = device != null
&& device.getDisplayDeviceInfoLocked().type != Display.TYPE_INTERNAL;
enableDisplayLocked(display, isEnabled);
enableDisplayLocked(display, device != null);
return display;
}
@@ -582,7 +571,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
final Layout layoutSet = mDeviceStateToLayoutMap.get(DeviceStateToLayoutMap.STATE_DEFAULT);
final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
final boolean isDefault = (info.flags & DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY) != 0;
layoutSet.createDisplayLocked(info.address, isDefault);
layoutSet.createDisplayLocked(info.address, isDefault, true /* isEnabled */);
}
private int assignLayerStackLocked(int displayId) {

View File

@@ -57,7 +57,7 @@ public class Layout {
* @return The new layout.
*/
public Display createDisplayLocked(
@NonNull DisplayAddress address, boolean isDefault) {
@NonNull DisplayAddress address, boolean isDefault, boolean isEnabled) {
if (contains(address)) {
Slog.w(TAG, "Attempting to add second definition for display-device: " + address);
return null;
@@ -74,7 +74,7 @@ public class Layout {
// different layouts, a logical display can be destroyed and later recreated with the
// same logical display ID.
final int logicalDisplayId = assignDisplayIdLocked(isDefault);
final Display layout = new Display(address, logicalDisplayId);
final Display layout = new Display(address, logicalDisplayId, isEnabled);
mDisplays.add(layout);
return layout;
@@ -130,17 +130,25 @@ public class Layout {
* Describes how a {@link LogicalDisplay} is built from {@link DisplayDevice}s.
*/
public static class Display {
// 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;
Display(@NonNull DisplayAddress address, int logicalDisplayId) {
// Indicates that this display is not usable and should remain off.
private final boolean mIsEnabled;
Display(@NonNull DisplayAddress address, int logicalDisplayId, boolean isEnabled) {
mAddress = address;
mLogicalDisplayId = logicalDisplayId;
mIsEnabled = isEnabled;
}
@Override
public String toString() {
return "{addr: " + mAddress + ", dispId: " + mLogicalDisplayId + "}";
return "{addr: " + mAddress + ", dispId: " + mLogicalDisplayId
+ "(" + (mIsEnabled ? "ON" : "OFF") + ")}";
}
public DisplayAddress getAddress() {
@@ -150,5 +158,9 @@ public class Layout {
public int getLogicalDisplayId() {
return mLogicalDisplayId;
}
public boolean isEnabled() {
return mIsEnabled;
}
}
}

View File

@@ -30,7 +30,6 @@ xsd_config {
gen_writer: true,
}
xsd_config {
name: "display-device-config",
srcs: ["display-device-config/display-device-config.xsd"],
@@ -38,6 +37,12 @@ xsd_config {
package_name: "com.android.server.display.config",
}
xsd_config {
name: "display-layout-config",
srcs: ["display-layout-config/display-layout-config.xsd"],
api_dir: "display-layout-config/schema",
package_name: "com.android.server.display.config.layout",
}
xsd_config {
name: "cec-config",

View File

@@ -1,23 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (C) 2020 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.
-->
Copyright (C) 2020 The Android Open Source Project
<!-- This defines the format of the XML file generated by
~ com.android.compat.annotation.ChangeIdProcessor annotation processor (from
~ tools/platform-compat), and is parsed in com/android/server/compat/CompatConfig.java.
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.
-->
<!--
This defines the format of the XML file used to provide static configuration values
for the displays on a device.
It is parsed in com/android/server/display/DisplayDeviceConfig.java
-->
<xs:schema version="2.0"
elementFormDefault="qualified"

View File

@@ -0,0 +1,3 @@
include /services/core/java/com/android/server/display/OWNERS
flc@google.com

View File

@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (C) 2021 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.
-->
<!--
This defines the format of the XML file used to defines how displays are laid out
for a given device-state.
It is parsed in com/android/server/display/layout/DeviceStateToLayoutMap.java
More information on device-state can be found in DeviceStateManager.java
-->
<xs:schema version="2.0"
elementFormDefault="qualified"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="layouts">
<xs:complexType>
<xs:sequence>
<xs:element type="layout" name="layout" minOccurs="1" maxOccurs="unbounded" />
</xs:sequence>
</xs:complexType>
<!-- Ensures only one layout is allowed per device state. -->
<xs:unique name="UniqueState">
<xs:selector xpath="layout" />
<xs:field xpath="@state" />
</xs:unique>
</xs:element>
<!-- Type definitions -->
<xs:complexType name="layout">
<xs:sequence>
<xs:element name="state" type="xs:nonNegativeInteger" />
<xs:element name="display" type="display" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="display">
<xs:sequence>
<xs:element name="address" type="xs:nonNegativeInteger"/>
</xs:sequence>
<xs:attribute name="enabled" type="xs:boolean" use="optional" />
<xs:attribute name="isDefault" type="xs:boolean" use="optional" />
</xs:complexType>
</xs:schema>

View File

@@ -0,0 +1,34 @@
// Signature format: 2.0
package com.android.server.display.config.layout {
public class Display {
ctor public Display();
method public java.math.BigInteger getAddress();
method public boolean getEnabled();
method public boolean getIsDefault();
method public void setAddress(java.math.BigInteger);
method public void setEnabled(boolean);
method public void setIsDefault(boolean);
}
public class Layout {
ctor public Layout();
method public java.util.List<com.android.server.display.config.layout.Display> getDisplay();
method public java.math.BigInteger getState();
method public void setState(java.math.BigInteger);
}
public class Layouts {
ctor public Layouts();
method public java.util.List<com.android.server.display.config.layout.Layout> getLayout();
}
public class XmlParser {
ctor public XmlParser();
method public static com.android.server.display.config.layout.Layouts read(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException;
method public static String readText(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
method public static void skip(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
}
}

View File

@@ -0,0 +1 @@
// Signature format: 2.0

View File

@@ -94,8 +94,7 @@ public class LogicalDisplayMapperTest {
// Disable binder caches in this process.
PropertyInvalidatedCache.disableForTestMode();
mLogicalDisplayMapper = new LogicalDisplayMapper(
mContext, mDisplayDeviceRepo, mListenerMock);
mLogicalDisplayMapper = new LogicalDisplayMapper(mDisplayDeviceRepo, mListenerMock);
}