diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 2a00b5a2e5135..655f42308a1f8 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -810,6 +810,9 @@ public final class DisplayInfo implements Parcelable {
if ((flags & Display.FLAG_TRUSTED) != 0) {
result.append(", FLAG_TRUSTED");
}
+ if ((flags & Display.FLAG_OWN_DISPLAY_GROUP) != 0) {
+ result.append(", FLAG_OWN_DISPLAY_GROUP");
+ }
return result.toString();
}
}
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 359bb69a417ed..b0a4c6e027cd6 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -673,6 +673,15 @@
-->
+
+
+
+
+
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 5d56eb7606795..aecb01551a26f 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3765,6 +3765,7 @@
+
diff --git a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
index d4556ed5f9fa0..91674cd8afa60 100644
--- a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
+++ b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
@@ -39,10 +39,6 @@ class 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 mLayoutMap = new SparseArray<>();
DeviceStateToLayoutMap(Context context) {
@@ -68,7 +64,7 @@ class DeviceStateToLayoutMap {
return layout;
}
- private Layout create(int state) {
+ private Layout createLayout(int state) {
if (mLayoutMap.contains(state)) {
Slog.e(TAG, "Attempted to create a second layout for state " + state);
return null;
@@ -79,10 +75,12 @@ class DeviceStateToLayoutMap {
return layout;
}
+ /**
+ * Loads config.xml-specified folded configurations for foldable devices.
+ */
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)
@@ -103,19 +101,23 @@ class DeviceStateToLayoutMap {
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) {
+ if (foldedDeviceStates.length == 0 || unfoldedDeviceStates.length == 0) {
return;
}
- // Create the folded state layout
- final Layout foldedLayout = create(STATE_FOLDED);
- foldedLayout.createDisplayLocked(
- DisplayAddress.fromPhysicalDisplayId(displayIds[0]), true /*isDefault*/);
+ for (int state : foldedDeviceStates) {
+ // Create the folded state layout
+ createLayout(state).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*/);
+ for (int state : unfoldedDeviceStates) {
+ // Create the unfolded state layout
+ createLayout(state).createDisplayLocked(
+ DisplayAddress.fromPhysicalDisplayId(displayIds[1]), true /*isDefault*/);
+ }
}
}
diff --git a/services/core/java/com/android/server/display/DisplayGroup.java b/services/core/java/com/android/server/display/DisplayGroup.java
index 663883ab2825b..2dcd5ccaf5575 100644
--- a/services/core/java/com/android/server/display/DisplayGroup.java
+++ b/services/core/java/com/android/server/display/DisplayGroup.java
@@ -30,6 +30,8 @@ public class DisplayGroup {
private final List mDisplays = new ArrayList<>();
private final int mGroupId;
+ private int mChangeCount;
+
DisplayGroup(int groupId) {
mGroupId = groupId;
}
@@ -45,11 +47,16 @@ public class DisplayGroup {
* @param display the {@link LogicalDisplay} to add to the Group
*/
void addDisplayLocked(LogicalDisplay display) {
- if (!mDisplays.contains(display)) {
+ if (!containsLocked(display)) {
+ mChangeCount++;
mDisplays.add(display);
}
}
+ boolean containsLocked(LogicalDisplay display) {
+ return mDisplays.contains(display);
+ }
+
/**
* Removes the provided {@code display} from the Group.
*
@@ -57,6 +64,7 @@ public class DisplayGroup {
* @return {@code true} if the {@code display} was removed; otherwise {@code false}
*/
boolean removeDisplayLocked(LogicalDisplay display) {
+ mChangeCount++;
return mDisplays.remove(display);
}
@@ -65,6 +73,11 @@ public class DisplayGroup {
return mDisplays.isEmpty();
}
+ /** Returns a count of the changes made to this display group. */
+ int getChangeCountLocked() {
+ return mChangeCount;
+ }
+
/** Returns the number of {@link LogicalDisplay LogicalDisplays} in the Group. */
int getSizeLocked() {
return mDisplays.size();
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 52149ee3a4ddf..ce0ba9f3231c8 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -485,13 +485,13 @@ public final class DisplayManagerService extends SystemService {
synchronized (mSyncRoot) {
long timeout = SystemClock.uptimeMillis()
+ mInjector.getDefaultDisplayDelayTimeout();
- while (mLogicalDisplayMapper.getLocked(Display.DEFAULT_DISPLAY) == null
+ while (mLogicalDisplayMapper.getDisplayLocked(Display.DEFAULT_DISPLAY) == null
|| mVirtualDisplayAdapter == null) {
long delay = timeout - SystemClock.uptimeMillis();
if (delay <= 0) {
throw new RuntimeException("Timeout waiting for default display "
+ "to be initialized. DefaultDisplay="
- + mLogicalDisplayMapper.getLocked(Display.DEFAULT_DISPLAY)
+ + mLogicalDisplayMapper.getDisplayLocked(Display.DEFAULT_DISPLAY)
+ ", mVirtualDisplayAdapter=" + mVirtualDisplayAdapter);
}
if (DEBUG) {
@@ -549,7 +549,7 @@ public final class DisplayManagerService extends SystemService {
mSystemReady = true;
// Just in case the top inset changed before the system was ready. At this point, any
// relevant configuration should be in place.
- recordTopInsetLocked(mLogicalDisplayMapper.getLocked(Display.DEFAULT_DISPLAY));
+ recordTopInsetLocked(mLogicalDisplayMapper.getDisplayLocked(Display.DEFAULT_DISPLAY));
updateSettingsLocked();
}
@@ -617,7 +617,7 @@ public final class DisplayManagerService extends SystemService {
@VisibleForTesting
void setDisplayInfoOverrideFromWindowManagerInternal(int displayId, DisplayInfo info) {
synchronized (mSyncRoot) {
- LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId);
+ final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId);
if (display != null) {
if (display.setDisplayInfoOverrideFromWindowManagerLocked(info)) {
handleLogicalDisplayChangedLocked(display);
@@ -632,7 +632,7 @@ public final class DisplayManagerService extends SystemService {
*/
private void getNonOverrideDisplayInfoInternal(int displayId, DisplayInfo outInfo) {
synchronized (mSyncRoot) {
- final LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId);
+ final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId);
if (display != null) {
display.getNonOverrideDisplayInfoLocked(outInfo);
}
@@ -691,8 +691,8 @@ public final class DisplayManagerService extends SystemService {
mDisplayStates.setValueAt(index, state);
mDisplayBrightnesses.setValueAt(index, brightnessState);
- runnable = updateDisplayStateLocked(
- mLogicalDisplayMapper.getLocked(displayId).getPrimaryDisplayDeviceLocked());
+ runnable = updateDisplayStateLocked(mLogicalDisplayMapper.getDisplayLocked(displayId)
+ .getPrimaryDisplayDeviceLocked());
}
// Setting the display power state can take hundreds of milliseconds
@@ -803,9 +803,9 @@ public final class DisplayManagerService extends SystemService {
private DisplayInfo getDisplayInfoInternal(int displayId, int callingUid) {
synchronized (mSyncRoot) {
- LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId);
+ final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId);
if (display != null) {
- DisplayInfo info =
+ final DisplayInfo info =
getDisplayInfoForFrameRateOverride(display.getFrameRateOverrides(),
display.getDisplayInfoLocked(), callingUid);
if (info.hasAccess(callingUid)
@@ -952,7 +952,7 @@ public final class DisplayManagerService extends SystemService {
private void requestColorModeInternal(int displayId, int colorMode) {
synchronized (mSyncRoot) {
- LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId);
+ final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId);
if (display != null &&
display.getRequestedColorModeLocked() != colorMode) {
display.setRequestedColorModeLocked(colorMode);
@@ -989,7 +989,7 @@ public final class DisplayManagerService extends SystemService {
mDisplayDeviceRepo.onDisplayDeviceEvent(device,
DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED);
- LogicalDisplay display = mLogicalDisplayMapper.getLocked(device);
+ final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(device);
if (display != null) {
return display.getDisplayIdLocked();
}
@@ -1203,7 +1203,7 @@ public final class DisplayManagerService extends SystemService {
// 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.getLocked(device);
+ final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(device);
final int state;
final int displayId = display.getDisplayIdLocked();
@@ -1364,7 +1364,7 @@ public final class DisplayManagerService extends SystemService {
float requestedRefreshRate, int requestedModeId, boolean preferMinimalPostProcessing,
boolean inTraversal) {
synchronized (mSyncRoot) {
- LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId);
+ final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId);
if (display == null) {
return;
}
@@ -1406,7 +1406,7 @@ public final class DisplayManagerService extends SystemService {
private void setDisplayOffsetsInternal(int displayId, int x, int y) {
synchronized (mSyncRoot) {
- LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId);
+ final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId);
if (display == null) {
return;
}
@@ -1424,7 +1424,7 @@ public final class DisplayManagerService extends SystemService {
private void setDisplayScalingDisabledInternal(int displayId, boolean disable) {
synchronized (mSyncRoot) {
- final LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId);
+ final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId);
if (display == null) {
return;
}
@@ -1460,7 +1460,7 @@ public final class DisplayManagerService extends SystemService {
@Nullable
private IBinder getDisplayToken(int displayId) {
synchronized (mSyncRoot) {
- final LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId);
+ final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId);
if (display != null) {
final DisplayDevice device = display.getPrimaryDisplayDeviceLocked();
if (device != null) {
@@ -1478,7 +1478,7 @@ public final class DisplayManagerService extends SystemService {
if (token == null) {
return null;
}
- final LogicalDisplay logicalDisplay = mLogicalDisplayMapper.getLocked(displayId);
+ final LogicalDisplay logicalDisplay = mLogicalDisplayMapper.getDisplayLocked(displayId);
if (logicalDisplay == null) {
return null;
}
@@ -1611,15 +1611,16 @@ public final class DisplayManagerService extends SystemService {
// Find the logical display that the display device is showing.
// Certain displays only ever show their own content.
- LogicalDisplay display = mLogicalDisplayMapper.getLocked(device);
+ LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(device);
if (!ownContent) {
if (display != null && !display.hasContentLocked()) {
// If the display does not have any content of its own, then
// automatically mirror the requested logical display contents if possible.
- display = mLogicalDisplayMapper.getLocked(device.getDisplayIdToMirrorLocked());
+ display = mLogicalDisplayMapper.getDisplayLocked(
+ device.getDisplayIdToMirrorLocked());
}
if (display == null) {
- display = mLogicalDisplayMapper.getLocked(Display.DEFAULT_DISPLAY);
+ display = mLogicalDisplayMapper.getDisplayLocked(Display.DEFAULT_DISPLAY);
}
}
@@ -1896,9 +1897,9 @@ public final class DisplayManagerService extends SystemService {
@VisibleForTesting
DisplayDeviceInfo getDisplayDeviceInfoInternal(int displayId) {
synchronized (mSyncRoot) {
- LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId);
+ final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId);
if (display != null) {
- DisplayDevice displayDevice = display.getPrimaryDisplayDeviceLocked();
+ final DisplayDevice displayDevice = display.getPrimaryDisplayDeviceLocked();
return displayDevice.getDisplayDeviceInfoLocked();
}
return null;
@@ -1908,9 +1909,9 @@ public final class DisplayManagerService extends SystemService {
@VisibleForTesting
int getDisplayIdToMirrorInternal(int displayId) {
synchronized (mSyncRoot) {
- LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId);
+ final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId);
if (display != null) {
- DisplayDevice displayDevice = display.getPrimaryDisplayDeviceLocked();
+ final DisplayDevice displayDevice = display.getPrimaryDisplayDeviceLocked();
return displayDevice.getDisplayIdToMirrorLocked();
}
return Display.INVALID_DISPLAY;
@@ -1992,7 +1993,8 @@ public final class DisplayManagerService extends SystemService {
ArraySet uids;
synchronized (mSyncRoot) {
int displayId = msg.arg1;
- LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId);
+ final LogicalDisplay display =
+ mLogicalDisplayMapper.getDisplayLocked(displayId);
uids = display.getPendingFrameRateOverrideUids();
display.clearPendingFrameRateOverrideUids();
}
@@ -2586,7 +2588,7 @@ public final class DisplayManagerService extends SystemService {
@Override // Binder call
public boolean isMinimalPostProcessingRequested(int displayId) {
synchronized (mSyncRoot) {
- return mLogicalDisplayMapper.getLocked(displayId)
+ return mLogicalDisplayMapper.getDisplayLocked(displayId)
.getRequestedMinimalPostProcessingLocked();
}
}
@@ -2831,7 +2833,7 @@ public final class DisplayManagerService extends SystemService {
@Override
public Point getDisplayPosition(int displayId) {
synchronized (mSyncRoot) {
- LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId);
+ final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId);
if (display != null) {
return display.getDisplayPosition();
}
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 20b133ce4d0a2..d9570c710b154 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -17,11 +17,11 @@
package com.android.server.display;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.display.DisplayManagerInternal;
import android.util.ArraySet;
-import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
import android.view.DisplayEventReceiver;
@@ -64,12 +64,14 @@ import java.util.Objects;
*/
final class LogicalDisplay {
private static final String TAG = "LogicalDisplay";
- private final DisplayInfo mBaseDisplayInfo = new DisplayInfo();
// The layer stack we use when the display has been blanked to prevent any
// of its content from appearing.
private static final int BLANK_LAYER_STACK = -1;
+ private static final DisplayInfo EMPTY_DISPLAY_INFO = new DisplayInfo();
+
+ private final DisplayInfo mBaseDisplayInfo = new DisplayInfo();
private final int mDisplayId;
private final int mLayerStack;
@@ -297,7 +299,7 @@ final class LogicalDisplay {
// Check whether logical display has become invalid.
if (!deviceRepo.containsLocked(mPrimaryDisplayDevice)) {
- mPrimaryDisplayDevice = null;
+ setPrimaryDisplayDeviceLocked(null);
return;
}
@@ -684,18 +686,28 @@ final class LogicalDisplay {
* @param targetDisplay The display with which to swap display-devices.
* @return {@code true} if the displays were swapped, {@code false} otherwise.
*/
- public boolean swapDisplaysLocked(@NonNull LogicalDisplay targetDisplay) {
- final DisplayDevice targetDevice = targetDisplay.getPrimaryDisplayDeviceLocked();
- if (mPrimaryDisplayDevice == null || targetDevice == null) {
- Slog.e(TAG, "Missing display device during swap: " + mPrimaryDisplayDevice + " , "
- + targetDevice);
- return false;
- }
+ public void swapDisplaysLocked(@NonNull LogicalDisplay targetDisplay) {
+ final DisplayDevice oldTargetDevice =
+ targetDisplay.setPrimaryDisplayDeviceLocked(mPrimaryDisplayDevice);
+ setPrimaryDisplayDeviceLocked(oldTargetDevice);
+ }
- final DisplayDevice tmpDevice = mPrimaryDisplayDevice;
- mPrimaryDisplayDevice = targetDisplay.mPrimaryDisplayDevice;
- targetDisplay.mPrimaryDisplayDevice = tmpDevice;
- return true;
+ /**
+ * Sets the primary display device to the specified device.
+ *
+ * @param device The new device to set.
+ * @return The previously set display device.
+ */
+ public DisplayDevice setPrimaryDisplayDeviceLocked(@Nullable DisplayDevice device) {
+ final DisplayDevice old = mPrimaryDisplayDevice;
+ mPrimaryDisplayDevice = device;
+
+ // Reset all our display info data
+ mPrimaryDisplayDeviceInfo = null;
+ mBaseDisplayInfo.copyFrom(EMPTY_DISPLAY_INFO);
+ mInfo.set(null);
+
+ return old;
}
/**
@@ -718,8 +730,8 @@ final class LogicalDisplay {
public void dumpLocked(PrintWriter pw) {
pw.println("mDisplayId=" + mDisplayId);
- pw.println("mLayerStack=" + mLayerStack);
pw.println("mIsEnabled=" + mIsEnabled);
+ pw.println("mLayerStack=" + mLayerStack);
pw.println("mHasContent=" + mHasContent);
pw.println("mDesiredDisplayModeSpecs={" + mDesiredDisplayModeSpecs + "}");
pw.println("mRequestedColorMode=" + mRequestedColorMode);
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index a3ff534e336e1..2bcc35cf176f9 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -17,13 +17,16 @@
package com.android.server.display;
import android.content.Context;
+import android.hardware.devicestate.DeviceStateManager;
import android.os.SystemProperties;
import android.text.TextUtils;
import android.util.IndentingPrintWriter;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
import android.view.Display;
-import android.view.DisplayEventReceiver;
+import android.view.DisplayAddress;
import android.view.DisplayInfo;
import com.android.server.display.layout.Layout;
@@ -74,24 +77,50 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
private final boolean mSingleDisplayDemoMode;
/**
- * List of all logical displays indexed by logical display id.
+ * Map 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 mLogicalDisplays =
new SparseArray();
- /** A mapping from logical display id to display group. */
- private final SparseArray mDisplayIdToGroupMap = new SparseArray<>();
+ /** Map of all display groups indexed by display group id. */
+ private final SparseArray mDisplayGroups = new SparseArray<>();
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.
+ * Any entry in here requires us to send a {@link LOGICAL_DISPLAY_EVENT_REMOVED} event when it
+ * is deleted or {@link LOGICAL_DISPLAY_EVENT_CHANGED} when it is changed.
+ */
+ private final SparseBooleanArray mUpdatedLogicalDisplays = new SparseBooleanArray();
+
+ /**
+ * Keeps track of all the display groups that we already told other people about. IOW, if a
+ * display group is in this array, then we *must* send change and remove notifications for it
+ * because other components know about them. Also, what this array stores is a change counter
+ * for each group, so we know if the group itself has changes since we last sent out a
+ * notification. See {@link DisplayGroup#getChangeCountLocked}.
+ */
+ private final SparseIntArray mUpdatedDisplayGroups = new SparseIntArray();
+
+ /**
+ * Array used in {@link #updateLogicalDisplaysLocked} to track events that need to be sent out.
+ */
+ private final SparseIntArray mLogicalDisplaysToUpdate = new SparseIntArray();
+
+ /**
+ * Array used in {@link #updateLogicalDisplaysLocked} to track events that need to be sent out.
+ */
+ private final SparseIntArray mDisplayGroupsToUpdate = new SparseIntArray();
+
private int mNextNonDefaultGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
private Layout mCurrentLayout = null;
- private boolean mIsFolded = false;
+ private int mDeviceState = DeviceStateManager.INVALID_DEVICE_STATE;
LogicalDisplayMapper(Context context, DisplayDeviceRepository repo, Listener listener) {
mDisplayDeviceRepo = repo;
@@ -109,14 +138,23 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
public void onDisplayDeviceEventLocked(DisplayDevice device, int event) {
switch (event) {
case DisplayDeviceRepository.DISPLAY_DEVICE_EVENT_ADDED:
+ if (DEBUG) {
+ Slog.d(TAG, "Display device added: " + device.getDisplayDeviceInfoLocked());
+ }
handleDisplayDeviceAddedLocked(device);
break;
case DisplayDeviceRepository.DISPLAY_DEVICE_EVENT_CHANGED:
+ if (DEBUG) {
+ Slog.d(TAG, "Display device changed: " + device.getDisplayDeviceInfoLocked());
+ }
updateLogicalDisplaysLocked();
break;
case DisplayDeviceRepository.DISPLAY_DEVICE_EVENT_REMOVED:
+ if (DEBUG) {
+ Slog.d(TAG, "Display device removed: " + device.getDisplayDeviceInfoLocked());
+ }
updateLogicalDisplaysLocked();
break;
}
@@ -127,11 +165,11 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
mListener.onTraversalRequested();
}
- public LogicalDisplay getLocked(int displayId) {
+ public LogicalDisplay getDisplayLocked(int displayId) {
return mLogicalDisplays.get(displayId);
}
- public LogicalDisplay getLocked(DisplayDevice device) {
+ public LogicalDisplay getDisplayLocked(DisplayDevice device) {
final int count = mLogicalDisplays.size();
for (int i = 0; i < count; i++) {
LogicalDisplay display = mLogicalDisplays.valueAt(i);
@@ -166,16 +204,25 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
}
}
- public DisplayGroup getDisplayGroupLocked(int groupId) {
- final int size = mDisplayIdToGroupMap.size();
+ public int getDisplayGroupIdFromDisplayIdLocked(int displayId) {
+ final LogicalDisplay display = getDisplayLocked(displayId);
+ if (display == null) {
+ return Display.INVALID_DISPLAY_GROUP;
+ }
+
+ final int size = mDisplayGroups.size();
for (int i = 0; i < size; i++) {
- final DisplayGroup displayGroup = mDisplayIdToGroupMap.valueAt(i);
- if (displayGroup.getGroupId() == groupId) {
- return displayGroup;
+ final DisplayGroup displayGroup = mDisplayGroups.valueAt(i);
+ if (displayGroup.containsLocked(display)) {
+ return mDisplayGroups.keyAt(i);
}
}
- return null;
+ return Display.INVALID_DISPLAY_GROUP;
+ }
+
+ public DisplayGroup getDisplayGroupLocked(int groupId) {
+ return mDisplayGroups.get(groupId);
}
public void dumpLocked(PrintWriter pw) {
@@ -203,229 +250,339 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
}
void setDeviceStateLocked(int state) {
- boolean folded = false;
- for (int i = 0; i < mFoldedDeviceStates.length; i++) {
- if (state == mFoldedDeviceStates[i]) {
- folded = true;
- break;
- }
+ if (state != mDeviceState) {
+ resetLayoutLocked();
+ mDeviceState = state;
+ applyLayoutLocked();
+ updateLogicalDisplaysLocked();
}
- setDeviceFoldedLocked(folded);
- }
-
- void setDeviceFoldedLocked(boolean isFolded) {
- mIsFolded = isFolded;
-
- // 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);
- }
-
- final Layout layout = mDeviceStateToLayoutMap.get(state);
- if (layout == null) {
- return;
- }
- final Layout.Display displayLayout = layout.getById(Display.DEFAULT_DISPLAY);
- if (displayLayout == null) {
- return;
- }
- final DisplayDevice newDefaultDevice =
- mDisplayDeviceRepo.getByAddressLocked(displayLayout.getAddress());
- if (newDefaultDevice == null) {
- return;
- }
-
- final LogicalDisplay defaultDisplay = mLogicalDisplays.get(Display.DEFAULT_DISPLAY);
- mCurrentLayout = layout;
-
- // If we're already set up accurately, return early
- if (defaultDisplay.getPrimaryDisplayDeviceLocked() == newDefaultDevice) {
- return;
- }
-
- // 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;
- }
- 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);
- displayToSwap.setEnabled(false);
-
- // Update the world
- updateLogicalDisplaysLocked();
}
private void handleDisplayDeviceAddedLocked(DisplayDevice device) {
DisplayDeviceInfo deviceInfo = device.getDisplayDeviceInfoLocked();
- boolean isDefault = (deviceInfo.flags & DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY) != 0;
- if (isDefault && mLogicalDisplays.get(Display.DEFAULT_DISPLAY) != null) {
- Slog.w(TAG, "Ignoring attempt to add a second default display: " + deviceInfo);
- isDefault = false;
+ // Internal Displays need to have additional initialization.
+ // TODO: b/168208162 - This initializes a default dynamic display layout for INTERNAL
+ // devices, which will eventually just be a fallback in case no static layout definitions
+ // exist or cannot be loaded.
+ if (deviceInfo.type == Display.TYPE_INTERNAL) {
+ initializeInternalDisplayDeviceLocked(device);
}
- if (!isDefault && mSingleDisplayDemoMode) {
- Slog.i(TAG, "Not creating a logical display for a secondary display "
- + " because single display demo mode is enabled: " + deviceInfo);
- return;
- }
+ // Create a logical display for the new display device
+ LogicalDisplay display = createNewLogicalDisplayLocked(
+ device, Layout.assignDisplayIdLocked(false /*isDefault*/));
- final int displayId = Layout.assignDisplayIdLocked(isDefault);
- final int layerStack = assignLayerStackLocked(displayId);
-
- final DisplayGroup displayGroup;
- final boolean addNewDisplayGroup =
- isDefault || (deviceInfo.flags & DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP) != 0;
- if (addNewDisplayGroup) {
- final int groupId = assignDisplayGroupIdLocked(isDefault);
- displayGroup = new DisplayGroup(groupId);
- } else {
- displayGroup = mDisplayIdToGroupMap.get(Display.DEFAULT_DISPLAY);
- }
-
- LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device);
- display.updateDisplayGroupIdLocked(displayGroup.getGroupId());
- display.updateLocked(mDisplayDeviceRepo);
- if (!display.isValidLocked()) {
- // This should never happen currently.
- Slog.w(TAG, "Ignoring display device because the logical display "
- + "created from it was not considered valid: " + deviceInfo);
- return;
- }
-
- // 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);
-
- if (addNewDisplayGroup) {
- // Group added events happen before Logical Display added events.
- mListener.onDisplayGroupEventLocked(displayGroup.getGroupId(),
- LogicalDisplayMapper.DISPLAY_GROUP_EVENT_ADDED);
- }
-
- mListener.onLogicalDisplayEventLocked(display,
- LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_ADDED);
-
- if (!addNewDisplayGroup) {
- // Group changed events happen after Logical Display added events.
- mListener.onDisplayGroupEventLocked(displayGroup.getGroupId(),
- LogicalDisplayMapper.DISPLAY_GROUP_EVENT_CHANGED);
- }
-
- if (DEBUG) {
- Slog.d(TAG, "New Display added: " + display);
- }
+ applyLayoutLocked();
+ updateLogicalDisplaysLocked();
}
/**
- * Updates all existing logical displays given the current set of display devices.
- * Removes invalid logical displays. Sends notifications if needed.
+ * Updates the rest of the display system once all the changes are applied for display
+ * devices and logical displays. The includes releasing invalid/empty LogicalDisplays,
+ * creating/adjusting/removing DisplayGroups, and notifying the rest of the system of the
+ * relevant changes.
*/
private void updateLogicalDisplaysLocked() {
+ // Go through all the displays and figure out if they need to be updated.
+ // Loops in reverse so that displays can be removed during the loop without affecting the
+ // rest of the loop.
for (int i = mLogicalDisplays.size() - 1; i >= 0; i--) {
final int displayId = mLogicalDisplays.keyAt(i);
LogicalDisplay display = mLogicalDisplays.valueAt(i);
mTempDisplayInfo.copyFrom(display.getDisplayInfoLocked());
display.getNonOverrideDisplayInfoLocked(mTempNonOverrideDisplayInfo);
- DisplayEventReceiver.FrameRateOverride[] frameRatesOverrides =
- display.getFrameRateOverrides();
+
display.updateLocked(mDisplayDeviceRepo);
- final DisplayGroup changedDisplayGroup;
+ final DisplayInfo newDisplayInfo = display.getDisplayInfoLocked();
+ final boolean wasPreviouslyUpdated = mUpdatedLogicalDisplays.get(displayId);
+
+ // The display is no longer valid and needs to be removed.
if (!display.isValidLocked()) {
- mLogicalDisplays.removeAt(i);
- final DisplayGroup displayGroup = mDisplayIdToGroupMap.removeReturnOld(displayId);
- displayGroup.removeDisplayLocked(display);
+ mUpdatedLogicalDisplays.delete(displayId);
- mListener.onLogicalDisplayEventLocked(display,
- LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_REMOVED);
-
- changedDisplayGroup = displayGroup;
- } else if (!mTempDisplayInfo.equals(display.getDisplayInfoLocked())) {
- final int flags = display.getDisplayInfoLocked().flags;
- final DisplayGroup defaultDisplayGroup = mDisplayIdToGroupMap.get(
- Display.DEFAULT_DISPLAY);
- if ((flags & Display.FLAG_OWN_DISPLAY_GROUP) != 0) {
- // The display should have its own DisplayGroup.
- if (defaultDisplayGroup.removeDisplayLocked(display)) {
- final int groupId = assignDisplayGroupIdLocked(false);
- final DisplayGroup displayGroup = new DisplayGroup(groupId);
- displayGroup.addDisplayLocked(display);
- display.updateDisplayGroupIdLocked(groupId);
- mDisplayIdToGroupMap.append(display.getDisplayIdLocked(), displayGroup);
- mListener.onDisplayGroupEventLocked(displayGroup.getGroupId(),
- LogicalDisplayMapper.DISPLAY_GROUP_EVENT_ADDED);
- changedDisplayGroup = defaultDisplayGroup;
- } else {
- changedDisplayGroup = null;
- }
- } else {
- // The display should be a part of the default DisplayGroup.
- final DisplayGroup displayGroup = mDisplayIdToGroupMap.get(displayId);
- if (displayGroup != defaultDisplayGroup) {
- displayGroup.removeDisplayLocked(display);
- defaultDisplayGroup.addDisplayLocked(display);
- display.updateDisplayGroupIdLocked(defaultDisplayGroup.getGroupId());
- mListener.onDisplayGroupEventLocked(defaultDisplayGroup.getGroupId(),
- LogicalDisplayMapper.DISPLAY_GROUP_EVENT_CHANGED);
- mDisplayIdToGroupMap.put(displayId, defaultDisplayGroup);
- changedDisplayGroup = displayGroup;
- } else {
- changedDisplayGroup = null;
- }
+ // Remove from group
+ final DisplayGroup displayGroup = getDisplayGroupLocked(
+ getDisplayGroupIdFromDisplayIdLocked(displayId));
+ if (displayGroup != null) {
+ displayGroup.removeDisplayLocked(display);
}
- final String oldUniqueId = mTempDisplayInfo.uniqueId;
- final String newUniqueId = display.getDisplayInfoLocked().uniqueId;
- final int eventMsg = TextUtils.equals(oldUniqueId, newUniqueId)
- ? LOGICAL_DISPLAY_EVENT_CHANGED : LOGICAL_DISPLAY_EVENT_SWAPPED;
- mListener.onLogicalDisplayEventLocked(display, eventMsg);
+ if (wasPreviouslyUpdated) {
+ // The display isn't actually removed from our internal data structures until
+ // after the notification is sent; see {@link #sendUpdatesForDisplaysLocked}.
+ Slog.i(TAG, "Removing display: " + displayId);
+ mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_REMOVED);
+ } else {
+ // This display never left this class, safe to remove without notification
+ mLogicalDisplays.removeAt(i);
+ }
+ continue;
+
+ // The display is new.
+ } else if (!wasPreviouslyUpdated) {
+ Slog.i(TAG, "Adding new display: " + displayId + ": " + newDisplayInfo);
+ assignDisplayGroupLocked(display);
+ mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_ADDED);
+
+ // Underlying displays device has changed to a different one.
+ } else if (!TextUtils.equals(mTempDisplayInfo.uniqueId, newDisplayInfo.uniqueId)) {
+ // FLAG_OWN_DISPLAY_GROUP could have changed, recalculate just in case
+ assignDisplayGroupLocked(display);
+ mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_SWAPPED);
+
+ // Something about the display device has changed.
+ } else if (!mTempDisplayInfo.equals(newDisplayInfo)) {
+ // FLAG_OWN_DISPLAY_GROUP could have changed, recalculate just in case
+ assignDisplayGroupLocked(display);
+ mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_CHANGED);
+
+ // Display frame rate overrides changed.
} else if (!display.getPendingFrameRateOverrideUids().isEmpty()) {
- mListener.onLogicalDisplayEventLocked(display,
- LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED);
- changedDisplayGroup = null;
+ mLogicalDisplaysToUpdate.put(
+ displayId, LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED);
+
+ // Non-override display values changed.
} else {
- // While applications shouldn't know nor care about the non-overridden info, we
+ // While application shouldn't know nor care about the non-overridden info, we
// still need to let WindowManager know so it can update its own internal state for
// things like display cutouts.
display.getNonOverrideDisplayInfoLocked(mTempDisplayInfo);
if (!mTempNonOverrideDisplayInfo.equals(mTempDisplayInfo)) {
- mListener.onLogicalDisplayEventLocked(display,
- LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_CHANGED);
+ mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_CHANGED);
}
- changedDisplayGroup = null;
}
- // CHANGED and REMOVED DisplayGroup events should always fire after Display events.
- if (changedDisplayGroup != null) {
- final int event = changedDisplayGroup.isEmptyLocked()
- ? LogicalDisplayMapper.DISPLAY_GROUP_EVENT_REMOVED
- : LogicalDisplayMapper.DISPLAY_GROUP_EVENT_CHANGED;
- mListener.onDisplayGroupEventLocked(changedDisplayGroup.getGroupId(), event);
+ mUpdatedLogicalDisplays.put(displayId, true);
+ }
+
+ // Go through the groups and do the same thing. We do this after displays since group
+ // information can change in the previous loop.
+ // Loops in reverse so that groups can be removed during the loop without affecting the
+ // rest of the loop.
+ for (int i = mDisplayGroups.size() - 1; i >= 0; i--) {
+ final int groupId = mDisplayGroups.keyAt(i);
+ final DisplayGroup group = mDisplayGroups.valueAt(i);
+ final boolean wasPreviouslyUpdated = mUpdatedDisplayGroups.indexOfKey(groupId) < 0;
+ final int changeCount = group.getChangeCountLocked();
+
+ if (group.isEmptyLocked()) {
+ mUpdatedDisplayGroups.delete(groupId);
+ if (wasPreviouslyUpdated) {
+ mDisplayGroupsToUpdate.put(groupId, DISPLAY_GROUP_EVENT_REMOVED);
+ }
+ continue;
+ } else if (!wasPreviouslyUpdated) {
+ mDisplayGroupsToUpdate.put(groupId, DISPLAY_GROUP_EVENT_ADDED);
+ } else if (mUpdatedDisplayGroups.get(groupId) != changeCount) {
+ mDisplayGroupsToUpdate.put(groupId, DISPLAY_GROUP_EVENT_CHANGED);
+ }
+ mUpdatedDisplayGroups.put(groupId, changeCount);
+ }
+
+ // Send the display and display group updates in order by message type. This is important
+ // to ensure that addition and removal notifications happen in the right order.
+ sendUpdatesForGroupsLocked(DISPLAY_GROUP_EVENT_ADDED);
+ sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_REMOVED);
+ sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_CHANGED);
+ sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED);
+ sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_ADDED);
+ sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_SWAPPED);
+ sendUpdatesForGroupsLocked(DISPLAY_GROUP_EVENT_CHANGED);
+ sendUpdatesForGroupsLocked(DISPLAY_GROUP_EVENT_REMOVED);
+
+ mLogicalDisplaysToUpdate.clear();
+ mDisplayGroupsToUpdate.clear();
+ }
+
+ /**
+ * Send the specified message for all relevant displays in the specified display-to-message map.
+ */
+ private void sendUpdatesForDisplaysLocked(int msg) {
+ for (int i = mLogicalDisplaysToUpdate.size() - 1; i >= 0; --i) {
+ final int currMsg = mLogicalDisplaysToUpdate.valueAt(i);
+ if (currMsg != msg) {
+ continue;
+ }
+
+ final int id = mLogicalDisplaysToUpdate.keyAt(i);
+ mListener.onLogicalDisplayEventLocked(getDisplayLocked(id), msg);
+ if (msg == LOGICAL_DISPLAY_EVENT_REMOVED) {
+ // We wait until we sent the EVENT_REMOVED event before actually removing the
+ // display.
+ mLogicalDisplays.delete(id);
}
}
}
- private int assignDisplayGroupIdLocked(boolean isDefault) {
- return isDefault ? Display.DEFAULT_DISPLAY_GROUP : mNextNonDefaultGroupId++;
+ /**
+ * Send the specified message for all relevant display groups in the specified message map.
+ */
+ private void sendUpdatesForGroupsLocked(int msg) {
+ for (int i = mDisplayGroupsToUpdate.size() - 1; i >= 0; --i) {
+ final int currMsg = mDisplayGroupsToUpdate.valueAt(i);
+ if (currMsg != msg) {
+ continue;
+ }
+
+ final int id = mDisplayGroupsToUpdate.keyAt(i);
+ mListener.onDisplayGroupEventLocked(id, msg);
+ if (msg == DISPLAY_GROUP_EVENT_REMOVED) {
+ // We wait until we sent the EVENT_REMOVED event before actually removing the
+ // group.
+ mDisplayGroups.delete(id);
+ }
+ }
+ }
+
+ private void assignDisplayGroupLocked(LogicalDisplay display) {
+ final int displayId = display.getDisplayIdLocked();
+
+ // Get current display group data
+ int groupId = getDisplayGroupIdFromDisplayIdLocked(displayId);
+ final DisplayGroup oldGroup = getDisplayGroupLocked(groupId);
+
+ // Get the new display group if a change is needed
+ final DisplayInfo info = display.getDisplayInfoLocked();
+ final boolean needsOwnDisplayGroup = (info.flags & Display.FLAG_OWN_DISPLAY_GROUP) != 0;
+ final boolean hasOwnDisplayGroup = groupId != Display.DEFAULT_DISPLAY_GROUP;
+ if (groupId == Display.INVALID_DISPLAY_GROUP
+ || hasOwnDisplayGroup != needsOwnDisplayGroup) {
+ groupId = assignDisplayGroupIdLocked(needsOwnDisplayGroup);
+ }
+
+ // Create a new group if needed
+ DisplayGroup newGroup = getDisplayGroupLocked(groupId);
+ if (newGroup == null) {
+ newGroup = new DisplayGroup(groupId);
+ mDisplayGroups.append(groupId, newGroup);
+ }
+ if (oldGroup != newGroup) {
+ if (oldGroup != null) {
+ oldGroup.removeDisplayLocked(display);
+ }
+ newGroup.addDisplayLocked(display);
+ display.updateDisplayGroupIdLocked(groupId);
+ Slog.i(TAG, "Setting new display group " + groupId + " for display "
+ + displayId + ", from previous group: "
+ + (oldGroup != null ? oldGroup.getGroupId() : "null"));
+ }
+ }
+
+ /**
+ * 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.
+ */
+ private void resetLayoutLocked() {
+ final Layout layout = mDeviceStateToLayoutMap.get(mDeviceState);
+ for (int i = layout.size() - 1; i >= 0; i--) {
+ final Layout.Display displayLayout = layout.getAt(i);
+ final LogicalDisplay display = getDisplayLocked(displayLayout.getLogicalDisplayId());
+ if (display != null) {
+ enableDisplayLocked(display, false);
+ }
+ }
+ }
+
+
+ /**
+ * Apply (or reapply) the currently selected display layout.
+ */
+ private void applyLayoutLocked() {
+ final Layout layout = mDeviceStateToLayoutMap.get(mDeviceState);
+ mCurrentLayout = layout;
+ Slog.i(TAG, "Applying the display layout for device state(" + mDeviceState
+ + "): " + layout);
+
+ // Go through each of the displays in the current layout set.
+ final int size = layout.size();
+ for (int i = 0; i < size; i++) {
+ final Layout.Display displayLayout = layout.getAt(i);
+
+ // 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.
+ final DisplayAddress address = displayLayout.getAddress();
+ final DisplayDevice device = mDisplayDeviceRepo.getByAddressLocked(address);
+ if (device == null) {
+ Slog.w(TAG, "The display device (" + address + "), is not available"
+ + " for the display state " + mDeviceState);
+ continue;
+ }
+
+ // Now that we have a display-device, we need a LogicalDisplay to map it to. Find the
+ // right one, if it doesn't exist, create a new one.
+ final int logicalDisplayId = displayLayout.getLogicalDisplayId();
+ LogicalDisplay newDisplay = getDisplayLocked(logicalDisplayId);
+ if (newDisplay == null) {
+ newDisplay = createNewLogicalDisplayLocked(
+ null /*displayDevice*/, logicalDisplayId);
+ }
+
+ // Now swap the underlying display devices between the old display and the new display
+ final LogicalDisplay oldDisplay = getDisplayLocked(device);
+ if (newDisplay != oldDisplay) {
+ newDisplay.swapDisplaysLocked(oldDisplay);
+ }
+ enableDisplayLocked(newDisplay, true);
+ }
+ }
+
+
+ /**
+ * Creates a new logical display for the specified device and display Id and adds it to the list
+ * of logical displays.
+ *
+ * @param device The device to associate with the LogicalDisplay.
+ * @param displayId The display ID to give the new display. If invalid, a new ID is assigned.
+ * @param isDefault Indicates if we are creating the default display.
+ * @return The new logical display if created, null otherwise.
+ */
+ private LogicalDisplay createNewLogicalDisplayLocked(DisplayDevice device, int displayId) {
+ final int layerStack = assignLayerStackLocked(displayId);
+ 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);
+
+ return display;
+ }
+
+ private void enableDisplayLocked(LogicalDisplay display, boolean isEnabled) {
+ final int displayId = display.getDisplayIdLocked();
+ final DisplayInfo info = display.getDisplayInfoLocked();
+
+ final boolean disallowSecondaryDisplay = mSingleDisplayDemoMode
+ && (info.type != Display.TYPE_INTERNAL);
+ if (isEnabled && disallowSecondaryDisplay) {
+ Slog.i(TAG, "Not creating a logical display for a secondary display because single"
+ + " display demo mode is enabled: " + display.getDisplayInfoLocked());
+ isEnabled = false;
+ }
+
+ display.setEnabled(isEnabled);
+ }
+
+ private int assignDisplayGroupIdLocked(boolean isOwnDisplayGroup) {
+ return isOwnDisplayGroup ? mNextNonDefaultGroupId++ : Display.DEFAULT_DISPLAY_GROUP;
+ }
+
+ private void initializeInternalDisplayDeviceLocked(DisplayDevice device) {
+ // We always want to make sure that our default display layout creates a logical
+ // display for every internal display device that is found.
+ // To that end, when we are notified of a new internal display, we add it to
+ // the default definition if it is not already there.
+ 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);
}
private int assignLayerStackLocked(int displayId) {
diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
new file mode 100644
index 0000000000000..92221c9713d3c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -0,0 +1,328 @@
+/*
+ * 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 static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED;
+import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED;
+import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED;
+import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_ADDED;
+import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_REMOVED;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.verify;
+
+import android.app.PropertyInvalidatedCache;
+import android.content.Context;
+import android.os.Parcel;
+import android.os.Process;
+import android.platform.test.annotations.Presubmit;
+import android.view.Display;
+import android.view.DisplayAddress;
+import android.view.DisplayInfo;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class LogicalDisplayMapperTest {
+ private static int sUniqueTestDisplayId = 0;
+
+ private DisplayDeviceRepository mDisplayDeviceRepo;
+ private LogicalDisplayMapper mLogicalDisplayMapper;
+ private Context mContext;
+
+ @Mock LogicalDisplayMapper.Listener mListenerMock;
+
+ @Captor ArgumentCaptor mDisplayCaptor;
+
+ @Before
+ public void setUp() {
+ // Share classloader to allow package private access.
+ System.setProperty("dexmaker.share_classloader", "true");
+ MockitoAnnotations.initMocks(this);
+
+ mContext = InstrumentationRegistry.getContext();
+ mDisplayDeviceRepo = new DisplayDeviceRepository(
+ new DisplayManagerService.SyncRoot(),
+ new PersistentDataStore(new PersistentDataStore.Injector() {
+ @Override
+ public InputStream openRead() {
+ return null;
+ }
+
+ @Override
+ public OutputStream startWrite() {
+ return null;
+ }
+
+ @Override
+ public void finishWrite(OutputStream os, boolean success) {}
+ }));
+
+ // Disable binder caches in this process.
+ PropertyInvalidatedCache.disableForTestMode();
+
+ mLogicalDisplayMapper = new LogicalDisplayMapper(
+ mContext, mDisplayDeviceRepo, mListenerMock);
+ }
+
+
+ /////////////////
+ // Test Methods
+ /////////////////
+
+ @Test
+ public void testDisplayDeviceAddAndRemove_Internal() {
+ DisplayDevice device = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
+ DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY);
+
+ // add
+ LogicalDisplay displayAdded = add(device);
+ assertEquals(info(displayAdded).address, info(device).address);
+ assertEquals(Display.DEFAULT_DISPLAY, id(displayAdded));
+
+ // remove
+ mDisplayDeviceRepo.onDisplayDeviceEvent(device, DISPLAY_DEVICE_EVENT_REMOVED);
+ verify(mListenerMock).onLogicalDisplayEventLocked(
+ mDisplayCaptor.capture(), eq(LOGICAL_DISPLAY_EVENT_REMOVED));
+ LogicalDisplay displayRemoved = mDisplayCaptor.getValue();
+ assertEquals(Display.DEFAULT_DISPLAY, id(displayRemoved));
+ assertEquals(displayAdded, displayRemoved);
+ }
+
+ @Test
+ public void testDisplayDeviceAddAndRemove_NonInternalTypes() {
+ testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_EXTERNAL);
+ testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_WIFI);
+ testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_OVERLAY);
+ testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_VIRTUAL);
+ testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_UNKNOWN);
+
+ // Call the internal test again, just to verify that adding non-internal displays
+ // doesn't affect the ability for an internal display to become the default display.
+ testDisplayDeviceAddAndRemove_Internal();
+ }
+
+ @Test
+ public void testDisplayDeviceAdd_TwoInternalOneDefault() {
+ DisplayDevice device1 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, 0);
+ DisplayDevice device2 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
+ DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY);
+
+ LogicalDisplay display1 = add(device1);
+ assertEquals(info(display1).address, info(device1).address);
+ assertNotEquals(Display.DEFAULT_DISPLAY, id(display1));
+
+ LogicalDisplay display2 = add(device2);
+ assertEquals(info(display2).address, info(device2).address);
+ assertEquals(Display.DEFAULT_DISPLAY, id(display2));
+ }
+
+ @Test
+ public void testDisplayDeviceAdd_TwoInternalBothDefault() {
+ DisplayDevice device1 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
+ DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY);
+ DisplayDevice device2 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
+ DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY);
+
+ LogicalDisplay display1 = add(device1);
+ assertEquals(info(display1).address, info(device1).address);
+ assertEquals(Display.DEFAULT_DISPLAY, id(display1));
+
+ LogicalDisplay display2 = add(device2);
+ assertEquals(info(display2).address, info(device2).address);
+ // Despite the flags, we can only have one default display
+ assertNotEquals(Display.DEFAULT_DISPLAY, id(display2));
+ }
+
+ @Test
+ public void testGetDisplayIdsLocked() {
+ add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
+ DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY));
+ add(createDisplayDevice(Display.TYPE_EXTERNAL, 600, 800, 0));
+ add(createDisplayDevice(Display.TYPE_VIRTUAL, 600, 800, 0));
+
+ int [] ids = mLogicalDisplayMapper.getDisplayIdsLocked(Process.SYSTEM_UID);
+ assertEquals(3, ids.length);
+ Arrays.sort(ids);
+ assertEquals(Display.DEFAULT_DISPLAY, ids[0]);
+ }
+
+ @Test
+ public void testSingleDisplayGroup() {
+ LogicalDisplay display1 = add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
+ DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY));
+ LogicalDisplay display2 = add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, 0));
+ LogicalDisplay display3 = add(createDisplayDevice(Display.TYPE_VIRTUAL, 600, 800, 0));
+
+ assertEquals(Display.DEFAULT_DISPLAY_GROUP,
+ mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display1)));
+ assertEquals(Display.DEFAULT_DISPLAY_GROUP,
+ mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display2)));
+ assertEquals(Display.DEFAULT_DISPLAY_GROUP,
+ mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display3)));
+ }
+
+ @Test
+ public void testMultipleDisplayGroups() {
+ LogicalDisplay display1 = add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
+ DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY));
+ LogicalDisplay display2 = add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, 0));
+
+
+ TestDisplayDevice device3 = createDisplayDevice(Display.TYPE_VIRTUAL, 600, 800,
+ DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
+ LogicalDisplay display3 = add(device3);
+
+ assertEquals(Display.DEFAULT_DISPLAY_GROUP,
+ mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display1)));
+ assertEquals(Display.DEFAULT_DISPLAY_GROUP,
+ mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display2)));
+ assertNotEquals(Display.DEFAULT_DISPLAY_GROUP,
+ mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display3)));
+
+ // Now switch it back to the default group by removing the flag and issuing an update
+ DisplayDeviceInfo info = device3.getSourceInfo();
+ info.flags = info.flags & ~DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP;
+ mDisplayDeviceRepo.onDisplayDeviceEvent(device3, DISPLAY_DEVICE_EVENT_CHANGED);
+
+ // Verify the new group is correct.
+ assertEquals(Display.DEFAULT_DISPLAY_GROUP,
+ mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display3)));
+ }
+
+
+ /////////////////
+ // Helper Methods
+ /////////////////
+
+ private TestDisplayDevice createDisplayDevice(int type, int width, int height, int flags) {
+ return createDisplayDevice(new DisplayAddressImpl(), type, width, height, flags);
+ }
+
+ private TestDisplayDevice createDisplayDevice(
+ DisplayAddress address, int type, int width, int height, int flags) {
+ TestDisplayDevice device = new TestDisplayDevice();
+ DisplayDeviceInfo displayDeviceInfo = device.getSourceInfo();
+ displayDeviceInfo.type = type;
+ displayDeviceInfo.width = width;
+ displayDeviceInfo.height = height;
+ displayDeviceInfo.flags = flags;
+ displayDeviceInfo.address = new DisplayAddressImpl();
+ return device;
+ }
+
+ private DisplayDeviceInfo info(DisplayDevice device) {
+ return device.getDisplayDeviceInfoLocked();
+ }
+
+ private DisplayInfo info(LogicalDisplay display) {
+ return display.getDisplayInfoLocked();
+ }
+
+ private int id(LogicalDisplay display) {
+ return display.getDisplayIdLocked();
+ }
+
+ private LogicalDisplay add(DisplayDevice device) {
+ mDisplayDeviceRepo.onDisplayDeviceEvent(device, DISPLAY_DEVICE_EVENT_ADDED);
+ ArgumentCaptor displayCaptor =
+ ArgumentCaptor.forClass(LogicalDisplay.class);
+ verify(mListenerMock).onLogicalDisplayEventLocked(
+ displayCaptor.capture(), eq(LOGICAL_DISPLAY_EVENT_ADDED));
+ clearInvocations(mListenerMock);
+ return displayCaptor.getValue();
+ }
+
+ private void testDisplayDeviceAddAndRemove_NonInternal(int type) {
+ DisplayDevice device = createDisplayDevice(type, 600, 800, 0);
+
+ // add
+ LogicalDisplay displayAdded = add(device);
+ assertEquals(info(displayAdded).address, info(device).address);
+ assertNotEquals(Display.DEFAULT_DISPLAY, id(displayAdded));
+
+ // remove
+ mDisplayDeviceRepo.onDisplayDeviceEvent(device, DISPLAY_DEVICE_EVENT_REMOVED);
+ verify(mListenerMock).onLogicalDisplayEventLocked(
+ mDisplayCaptor.capture(), eq(LOGICAL_DISPLAY_EVENT_REMOVED));
+ LogicalDisplay displayRemoved = mDisplayCaptor.getValue();
+ assertNotEquals(Display.DEFAULT_DISPLAY, id(displayRemoved));
+ }
+
+ /**
+ * Create a custom {@link DisplayAddress} to ensure we're not relying on any specific
+ * display-address implementation in our code. Intentionally uses default object (reference)
+ * equality rules.
+ */
+ class DisplayAddressImpl extends DisplayAddress {
+ @Override
+ public void writeToParcel(Parcel out, int flags) { }
+ }
+
+ class TestDisplayDevice extends DisplayDevice {
+ private DisplayDeviceInfo mInfo = new DisplayDeviceInfo();
+ private DisplayDeviceInfo mSentInfo;
+
+ TestDisplayDevice() {
+ super(null, null, "test_display_" + sUniqueTestDisplayId++, mContext);
+ mInfo = new DisplayDeviceInfo();
+ }
+
+ @Override
+ public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
+ if (mSentInfo == null) {
+ mSentInfo = new DisplayDeviceInfo();
+ mSentInfo.copyFrom(mInfo);
+ }
+ return mSentInfo;
+ }
+
+ @Override
+ public void applyPendingDisplayDeviceInfoChangesLocked() {
+ mSentInfo = null;
+ }
+
+ @Override
+ public boolean hasStableUniqueId() {
+ return true;
+ }
+
+ public DisplayDeviceInfo getSourceInfo() {
+ return mInfo;
+ }
+ }
+}
+