summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Santos Cordon <santoscordon@google.com> 2021-02-04 11:08:47 +0000
committer Santos Cordon <santoscordon@google.com> 2021-02-10 17:09:12 +0000
commitffbaf23f2dd83f36ceeb4cf041910c2bcb82d2d4 (patch)
treeb64d7b79dca5137ad0868302dc4b553a9b611ec9
parent13e0d8cfc41718b28dc8e4a850ed94d5d5a73906 (diff)
Add display layout classes.
Adds in-code representation of display layouts. Uses new display layout and display-state-to-layout mappings to define layouts for foldable devices based on config variables. Remove the old foldable override adb command, since we can now switch using the device-state-manager commands. Test: manual, check foldable devices still work Test: atest frameworks/base/services/tests/servicestests/src/com/android/server/display Bug: 168208162 Change-Id: I09a154eaea99f70ec1207466b20bd9a4c8185306
-rw-r--r--services/core/java/com/android/server/display/DeviceStateToLayoutMap.java121
-rw-r--r--services/core/java/com/android/server/display/DisplayDeviceRepository.java12
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java7
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerShellCommand.java26
-rw-r--r--services/core/java/com/android/server/display/LogicalDisplay.java9
-rw-r--r--services/core/java/com/android/server/display/LogicalDisplayMapper.java145
-rw-r--r--services/core/java/com/android/server/display/layout/Layout.java154
7 files changed, 340 insertions, 134 deletions
diff --git a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
new file mode 100644
index 000000000000..d4556ed5f9fa
--- /dev/null
+++ b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
@@ -0,0 +1,121 @@
+/*
+ * 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.
+ */
+
+package com.android.server.display;
+
+import android.content.Context;
+import android.hardware.devicestate.DeviceStateManager;
+import android.text.TextUtils;
+import android.util.IndentingPrintWriter;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.DisplayAddress;
+
+import com.android.server.display.layout.Layout;
+
+import java.util.Arrays;
+
+/**
+ * Mapping from device states into {@link Layout}s. This allows us to map device
+ * states into specific layouts for the connected displays; particularly useful for
+ * foldable and multi-display devices where the default display and which displays are ON can
+ * change depending on the state of the device.
+ */
+class DeviceStateToLayoutMap {
+ private static final String TAG = "DeviceStateToLayoutMap";
+
+ public static final int STATE_DEFAULT = DeviceStateManager.INVALID_DEVICE_STATE;
+
+ // TODO - b/168208162 - Remove these when we check in static definitions for layouts
+ public static final int STATE_FOLDED = 100;
+ public static final int STATE_UNFOLDED = 101;
+
+ private final SparseArray<Layout> mLayoutMap = new SparseArray<>();
+
+ DeviceStateToLayoutMap(Context context) {
+ mLayoutMap.append(STATE_DEFAULT, new Layout());
+ loadFoldedDisplayConfig(context);
+ }
+
+ public void dumpLocked(IndentingPrintWriter ipw) {
+ ipw.println("DeviceStateToLayoutMap:");
+ ipw.increaseIndent();
+
+ ipw.println("Registered Layouts:");
+ for (int i = 0; i < mLayoutMap.size(); i++) {
+ ipw.println("state(" + mLayoutMap.keyAt(i) + "): " + mLayoutMap.valueAt(i));
+ }
+ }
+
+ Layout get(int state) {
+ Layout layout = mLayoutMap.get(state);
+ if (layout == null) {
+ layout = mLayoutMap.get(STATE_DEFAULT);
+ }
+ return layout;
+ }
+
+ private Layout create(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;
+ }
+
+ 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)
+ + "]");
+ 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);
+ // Only add folded states if folded state config is not empty
+ if (foldedDeviceStates.length == 0) {
+ return;
+ }
+
+ // Create the folded state layout
+ final Layout foldedLayout = create(STATE_FOLDED);
+ foldedLayout.createDisplayLocked(
+ DisplayAddress.fromPhysicalDisplayId(displayIds[0]), true /*isDefault*/);
+
+ // Create the unfolded state layout
+ final Layout unfoldedLayout = create(STATE_UNFOLDED);
+ unfoldedLayout.createDisplayLocked(
+ DisplayAddress.fromPhysicalDisplayId(displayIds[1]), true /*isDefault*/);
+ }
+}
diff --git a/services/core/java/com/android/server/display/DisplayDeviceRepository.java b/services/core/java/com/android/server/display/DisplayDeviceRepository.java
index 5c0fceb9b795..57f44864d2c0 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceRepository.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceRepository.java
@@ -19,6 +19,7 @@ package com.android.server.display;
import android.annotation.NonNull;
import android.util.Slog;
import android.view.Display;
+import android.view.DisplayAddress;
import com.android.internal.annotations.GuardedBy;
import com.android.server.display.DisplayManagerService.SyncRoot;
@@ -112,12 +113,11 @@ class DisplayDeviceRepository implements DisplayAdapter.Listener {
}
}
- public DisplayDevice getByIdLocked(@NonNull String uniqueId) {
- final int count = mDisplayDevices.size();
- for (int i = 0; i < count; i++) {
- final DisplayDevice d = mDisplayDevices.get(i);
- if (uniqueId.equals(d.getUniqueId())) {
- return d;
+ public DisplayDevice getByAddressLocked(@NonNull DisplayAddress address) {
+ for (int i = mDisplayDevices.size() - 1; i >= 0; i--) {
+ final DisplayDevice device = mDisplayDevices.get(i);
+ if (address.equals(device.getDisplayDeviceInfoLocked().address)) {
+ return device;
}
}
return null;
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 01fee5645475..aaf085a1923d 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1188,6 +1188,7 @@ public final class DisplayManagerService extends SystemService {
final LogicalDisplay display = mLogicalDisplayMapper.getLocked(device);
final int state;
final int displayId = display.getDisplayIdLocked();
+
if (display.isEnabled()) {
state = mDisplayStates.get(displayId);
} else {
@@ -1564,12 +1565,6 @@ public final class DisplayManagerService extends SystemService {
}
}
- void setFoldOverride(Boolean isFolded) {
- synchronized (mSyncRoot) {
- mLogicalDisplayMapper.setFoldOverrideLocked(isFolded);
- }
- }
-
private void clearViewportsLocked() {
mViewports.clear();
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
index aaea15a27e01..d1d04966bdec 100644
--- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
+++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
@@ -60,8 +60,6 @@ class DisplayManagerShellCommand extends ShellCommand {
return setDisplayModeDirectorLoggingEnabled(false);
case "dwb-set-cct":
return setAmbientColorTemperatureOverride();
- case "set-fold":
- return setFoldOverride();
default:
return handleDefaultCommands(cmd);
}
@@ -92,8 +90,6 @@ class DisplayManagerShellCommand extends ShellCommand {
pw.println(" Disable display mode director logging.");
pw.println(" dwb-set-cct CCT");
pw.println(" Sets the ambient color temperature override to CCT (use -1 to disable).");
- pw.println(" set-fold [fold|unfold|reset]");
- pw.println(" Simulates the 'fold' state of a device. 'reset' for default behavior.");
pw.println();
Intent.printIntentArgsHelp(pw , "");
}
@@ -165,26 +161,4 @@ class DisplayManagerShellCommand extends ShellCommand {
mService.setAmbientColorTemperatureOverride(cct);
return 0;
}
-
- private int setFoldOverride() {
- String state = getNextArg();
- if (state == null) {
- getErrPrintWriter().println("Error: no parameter specified for set-fold");
- return 1;
- }
- final Boolean isFolded;
- if ("fold".equals(state)) {
- isFolded = true;
- } else if ("unfold".equals(state)) {
- isFolded = false;
- } else if ("reset".equals(state)) {
- isFolded = null;
- } else {
- getErrPrintWriter().println("Error: Invalid fold state request: " + state);
- return 1;
- }
-
- mService.setFoldOverride(isFolded);
- return 0;
- }
}
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 80781d280d5e..20b133ce4d0a 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -32,6 +32,7 @@ import android.view.SurfaceControl;
import com.android.server.wm.utils.InsetUtils;
import java.io.PrintWriter;
+import java.io.StringWriter;
import java.util.Arrays;
import java.util.Objects;
@@ -718,6 +719,7 @@ final class LogicalDisplay {
public void dumpLocked(PrintWriter pw) {
pw.println("mDisplayId=" + mDisplayId);
pw.println("mLayerStack=" + mLayerStack);
+ pw.println("mIsEnabled=" + mIsEnabled);
pw.println("mHasContent=" + mHasContent);
pw.println("mDesiredDisplayModeSpecs={" + mDesiredDisplayModeSpecs + "}");
pw.println("mRequestedColorMode=" + mRequestedColorMode);
@@ -731,4 +733,11 @@ final class LogicalDisplay {
pw.println("mFrameRateOverrides=" + Arrays.toString(mFrameRateOverrides));
pw.println("mPendingFrameRateOverrideUids=" + mPendingFrameRateOverrideUids);
}
+
+ @Override
+ public String toString() {
+ StringWriter sw = new StringWriter();
+ dumpLocked(new PrintWriter(sw));
+ return sw.toString();
+ }
}
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 16c4b2641433..a054db533e3f 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -27,10 +27,10 @@ import android.view.Display;
import android.view.DisplayEventReceiver;
import android.view.DisplayInfo;
+import com.android.server.display.layout.Layout;
import java.io.PrintWriter;
import java.util.Arrays;
-import java.util.Objects;
import java.util.function.Consumer;
/**
@@ -75,40 +75,25 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
private final boolean mSingleDisplayDemoMode;
/**
- * Physical Display ID of the DisplayDevice to associate with the default LogicalDisplay
- * when {@link mIsFolded} is set to {@code true}.
- */
- private String mDisplayIdToUseWhenFolded;
-
- /**
- * Physical Display ID of the DisplayDevice to associate with the default LogicalDisplay
- * when {@link mIsFolded} is set to {@code false}.
- */
- private String mDisplayIdToUseWhenUnfolded;
-
- /** Overrides the folded state of the device. For use with ADB commands. */
- private Boolean mIsFoldedOverride;
-
- /** Saves the last device fold state. */
- private boolean mIsFolded;
-
- /**
* List of all logical displays indexed by logical display id.
* Any modification to mLogicalDisplays must invalidate the DisplayManagerGlobal cache.
* TODO: multi-display - Move the aforementioned comment?
*/
private final SparseArray<LogicalDisplay> mLogicalDisplays =
new SparseArray<LogicalDisplay>();
- private int mNextNonDefaultDisplayId = Display.DEFAULT_DISPLAY + 1;
- private int mNextNonDefaultGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
/** A mapping from logical display id to display group. */
private final SparseArray<DisplayGroup> mDisplayIdToGroupMap = new SparseArray<>();
private final DisplayDeviceRepository mDisplayDeviceRepo;
+ private final DeviceStateToLayoutMap mDeviceStateToLayoutMap;
private final Listener mListener;
private final int[] mFoldedDeviceStates;
+ private int mNextNonDefaultGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+ private Layout mCurrentLayout = null;
+ private boolean mIsFolded = false;
+
LogicalDisplayMapper(Context context, DisplayDeviceRepository repo, Listener listener) {
mDisplayDeviceRepo = repo;
mListener = listener;
@@ -118,7 +103,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
mFoldedDeviceStates = context.getResources().getIntArray(
com.android.internal.R.array.config_foldedDeviceStates);
- loadFoldedDisplayConfig(context);
+ mDeviceStateToLayoutMap = new DeviceStateToLayoutMap(context);
}
@Override
@@ -213,13 +198,12 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
ipw.increaseIndent();
ipw.println("mSingleDisplayDemoMode=" + mSingleDisplayDemoMode);
- ipw.println("mNextNonDefaultDisplayId=" + mNextNonDefaultDisplayId);
+
+ ipw.println("mCurrentLayout=" + mCurrentLayout);
final int logicalDisplayCount = mLogicalDisplays.size();
ipw.println();
ipw.println("Logical Displays: size=" + logicalDisplayCount);
-
-
for (int i = 0; i < logicalDisplayCount; i++) {
int displayId = mLogicalDisplays.keyAt(i);
LogicalDisplay display = mLogicalDisplays.valueAt(i);
@@ -229,6 +213,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
ipw.decreaseIndent();
ipw.println();
}
+ mDeviceStateToLayoutMap.dumpLocked(ipw);
}
void setDeviceStateLocked(int state) {
@@ -244,79 +229,55 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
void setDeviceFoldedLocked(boolean isFolded) {
mIsFolded = isFolded;
- if (mIsFoldedOverride != null) {
- isFolded = mIsFoldedOverride.booleanValue();
+
+ // Until we have fully functioning state mapping, use hardcoded states based on isFolded
+ final int state = mIsFolded ? DeviceStateToLayoutMap.STATE_FOLDED
+ : DeviceStateToLayoutMap.STATE_UNFOLDED;
+
+ if (DEBUG) {
+ Slog.d(TAG, "New device state: " + state);
}
- if (mDisplayIdToUseWhenFolded == null || mDisplayIdToUseWhenUnfolded == null
- || mLogicalDisplays.size() < 2) {
- // Do nothing if this behavior is disabled or there are less than two displays.
+ final Layout layout = mDeviceStateToLayoutMap.get(state);
+ if (layout == null) {
return;
}
-
- final DisplayDevice deviceFolded =
- mDisplayDeviceRepo.getByIdLocked(mDisplayIdToUseWhenFolded);
- final DisplayDevice deviceUnfolded =
- mDisplayDeviceRepo.getByIdLocked(mDisplayIdToUseWhenUnfolded);
- if (deviceFolded == null || deviceUnfolded == null) {
- // If the expected devices for folding functionality are not present, return early.
+ final Layout.Display displayLayout = layout.getById(Display.DEFAULT_DISPLAY);
+ if (displayLayout == null) {
return;
}
-
- // Find the associated LogicalDisplays for the configured "folding" DeviceDisplays.
- final LogicalDisplay displayFolded = getLocked(deviceFolded);
- final LogicalDisplay displayUnfolded = getLocked(deviceUnfolded);
- if (displayFolded == null || displayUnfolded == null) {
- // If the expected displays are not present, return early.
+ final DisplayDevice newDefaultDevice =
+ mDisplayDeviceRepo.getByAddressLocked(displayLayout.getAddress());
+ if (newDefaultDevice == null) {
return;
}
- // Find out which display is currently default and which is disabled.
final LogicalDisplay defaultDisplay = mLogicalDisplays.get(Display.DEFAULT_DISPLAY);
- final LogicalDisplay disabledDisplay;
- if (defaultDisplay == displayFolded) {
- disabledDisplay = displayUnfolded;
- } else if (defaultDisplay == displayUnfolded) {
- disabledDisplay = displayFolded;
- } else {
- // If neither folded or unfolded displays are currently set to the default display, we
- // are in an unknown state and it's best to log the error and bail.
- Slog.e(TAG, "Unexpected: when attempting to swap displays, neither of the two"
- + " configured displays were set up as the default display. Default: "
- + defaultDisplay.getDisplayInfoLocked() + ", ConfiguredDisplays: [ folded="
- + displayFolded.getDisplayInfoLocked() + ", unfolded="
- + displayUnfolded.getDisplayInfoLocked() + " ]");
+ mCurrentLayout = layout;
+
+ // If we're already set up accurately, return early
+ if (defaultDisplay.getPrimaryDisplayDeviceLocked() == newDefaultDevice) {
return;
}
- if (isFolded == (defaultDisplay == displayFolded)) {
- // Nothing to do, already in the right state.
+ // We need to swap the default display's display-device with the one that is supposed
+ // to be the default in the new layout.
+ final LogicalDisplay displayToSwap = getLocked(newDefaultDevice);
+ if (displayToSwap == null) {
+ Slog.w(TAG, "Canceling display swap - unexpected empty second display for: "
+ + newDefaultDevice);
return;
}
-
- // Everything was checked and we need to swap, lets swap.
- displayFolded.swapDisplaysLocked(displayUnfolded);
+ defaultDisplay.swapDisplaysLocked(displayToSwap);
// We ensure that the non-default Display is always forced to be off. This was likely
// already done in a previous iteration, but we do it with each swap in case something in
// the underlying LogicalDisplays changed: like LogicalDisplay recreation, for example.
defaultDisplay.setEnabled(true);
- disabledDisplay.setEnabled(false);
+ displayToSwap.setEnabled(false);
// Update the world
updateLogicalDisplaysLocked();
-
- if (DEBUG) {
- Slog.d(TAG, "Folded displays: isFolded: " + isFolded + ", defaultDisplay? "
- + defaultDisplay.getDisplayInfoLocked());
- }
- }
-
- void setFoldOverrideLocked(Boolean isFolded) {
- if (!Objects.equals(isFolded, mIsFoldedOverride)) {
- mIsFoldedOverride = isFolded;
- setDeviceFoldedLocked(mIsFolded);
- }
}
private void handleDisplayDeviceAddedLocked(DisplayDevice device) {
@@ -333,7 +294,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
return;
}
- final int displayId = assignDisplayIdLocked(isDefault);
+ final int displayId = Layout.assignDisplayIdLocked(isDefault);
final int layerStack = assignLayerStackLocked(displayId);
final DisplayGroup displayGroup;
@@ -356,8 +317,15 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
return;
}
- mLogicalDisplays.put(displayId, display);
+ // For foldable devices, we start the internal non-default displays as disabled.
+ // TODO - b/168208162 - this will be removed when we recalculate the layout with each
+ // display-device addition.
+ if (mFoldedDeviceStates.length > 0 && deviceInfo.type == Display.TYPE_INTERNAL
+ && !isDefault) {
+ display.setEnabled(false);
+ }
+ mLogicalDisplays.put(displayId, display);
displayGroup.addDisplayLocked(display);
mDisplayIdToGroupMap.append(displayId, displayGroup);
@@ -375,6 +343,10 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
mListener.onDisplayGroupEventLocked(displayGroup.getGroupId(),
LogicalDisplayMapper.DISPLAY_GROUP_EVENT_CHANGED);
}
+
+ if (DEBUG) {
+ Slog.d(TAG, "New Display added: " + display);
+ }
}
/**
@@ -466,10 +438,6 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
}
}
- private int assignDisplayIdLocked(boolean isDefault) {
- return isDefault ? Display.DEFAULT_DISPLAY : mNextNonDefaultDisplayId++;
- }
-
private int assignDisplayGroupIdLocked(boolean isDefault) {
return isDefault ? Display.DEFAULT_DISPLAY_GROUP : mNextNonDefaultGroupId++;
}
@@ -480,21 +448,6 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
return displayId;
}
- private void loadFoldedDisplayConfig(Context context) {
- final String[] displayIds = context.getResources().getStringArray(
- com.android.internal.R.array.config_internalFoldedPhysicalDisplayIds);
-
- if (displayIds.length != 2 || TextUtils.isEmpty(displayIds[0])
- || TextUtils.isEmpty(displayIds[1])) {
- Slog.w(TAG, "Folded display configuration invalid: [" + Arrays.toString(displayIds)
- + "]");
- return;
- }
-
- mDisplayIdToUseWhenFolded = displayIds[0];
- mDisplayIdToUseWhenUnfolded = displayIds[1];
- }
-
public interface Listener {
void onLogicalDisplayEventLocked(LogicalDisplay display, int event);
void onDisplayGroupEventLocked(int groupId, int event);
diff --git a/services/core/java/com/android/server/display/layout/Layout.java b/services/core/java/com/android/server/display/layout/Layout.java
new file mode 100644
index 000000000000..18f39e6ade1d
--- /dev/null
+++ b/services/core/java/com/android/server/display/layout/Layout.java
@@ -0,0 +1,154 @@
+/*
+ * 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.
+ */
+
+package com.android.server.display.layout;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.annotation.NonNull;
+import android.util.Slog;
+import android.view.DisplayAddress;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Holds a collection of {@link Display}s. A single instance of this class describes
+ * how to organize one or more DisplayDevices into LogicalDisplays for a particular device
+ * state. For example, there may be one instance of this class to describe display layout when
+ * a foldable device is folded, and a second instance for when the device is unfolded.
+ */
+public class Layout {
+ private static final String TAG = "Layout";
+ private static int sNextNonDefaultDisplayId = DEFAULT_DISPLAY + 1;
+
+ private final List<Display> mDisplays = new ArrayList<>(2);
+
+ /**
+ * @return The default display ID, or a new unique one to use.
+ */
+ public static int assignDisplayIdLocked(boolean isDefault) {
+ return isDefault ? DEFAULT_DISPLAY : sNextNonDefaultDisplayId++;
+ }
+
+ @Override
+ public String toString() {
+ return mDisplays.toString();
+ }
+
+ /**
+ * 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.
+ * @return The new layout.
+ */
+ public Display createDisplayLocked(
+ @NonNull DisplayAddress address, boolean isDefault) {
+ if (contains(address)) {
+ Slog.w(TAG, "Attempting to add second definition for display-device: " + address);
+ return null;
+ }
+
+ // See if we're dealing with the "default" display
+ if (isDefault && getById(DEFAULT_DISPLAY) != null) {
+ Slog.w(TAG, "Ignoring attempt to add a second default display: " + address);
+ isDefault = false;
+ }
+
+ // Assign a logical display ID and create the new display.
+ // 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 Display layout = new Display(address, logicalDisplayId);
+
+ mDisplays.add(layout);
+ return layout;
+ }
+
+ /**
+ * @param address The address to check.
+ *
+ * @return True if the specified address is used in this layout.
+ */
+ public boolean contains(@NonNull DisplayAddress address) {
+ final int size = mDisplays.size();
+ for (int i = 0; i < size; i++) {
+ if (address.equals(mDisplays.get(i).getAddress())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @param id The display ID to check.
+ *
+ * @return The display corresponding to the specified display ID.
+ */
+ public Display getById(int id) {
+ for (int i = 0; i < mDisplays.size(); i++) {
+ Display layout = mDisplays.get(i);
+ if (id == layout.getLogicalDisplayId()) {
+ return layout;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * @param index The index of the display to return.
+ *
+ * @return the display at the specified index.
+ */
+ public Display getAt(int index) {
+ return mDisplays.get(index);
+ }
+
+ /**
+ * @return The number of displays defined for this layout.
+ */
+ public int size() {
+ return mDisplays.size();
+ }
+
+ /**
+ * Describes how a {@link LogicalDisplay} is built from {@link DisplayDevice}s.
+ */
+ public static class Display {
+ private final DisplayAddress mAddress;
+ private final int mLogicalDisplayId;
+
+ Display(@NonNull DisplayAddress address, int logicalDisplayId) {
+ mAddress = address;
+ mLogicalDisplayId = logicalDisplayId;
+ }
+
+ @Override
+ public String toString() {
+ return "{addr: " + mAddress + ", dispId: " + mLogicalDisplayId + "}";
+ }
+
+ public DisplayAddress getAddress() {
+ return mAddress;
+ }
+
+ public int getLogicalDisplayId() {
+ return mLogicalDisplayId;
+ }
+ }
+}