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