diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 529aa6641c688..48f4d288d2b62 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -657,6 +657,12 @@ + + true + + diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 393a4eb83dd68..96641a6a019a3 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -430,8 +430,8 @@ public final class DisplayManagerService extends SystemService { mHandler = new DisplayManagerHandler(DisplayThread.get().getLooper()); mUiHandler = UiThread.getHandler(); mDisplayDeviceRepo = new DisplayDeviceRepository(mSyncRoot, mPersistentDataStore); - mLogicalDisplayMapper = new LogicalDisplayMapper(mDisplayDeviceRepo, - new LogicalDisplayListener()); + mLogicalDisplayMapper = new LogicalDisplayMapper(mContext, mDisplayDeviceRepo, + new LogicalDisplayListener(), mSyncRoot, mHandler); mDisplayModeDirector = new DisplayModeDirector(context, mHandler); mBrightnessSynchronizer = new BrightnessSynchronizer(mContext); Resources resources = mContext.getResources(); @@ -1269,7 +1269,7 @@ public final class DisplayManagerService extends SystemService { DisplayPowerController dpc = mDisplayPowerControllers.get(displayId); if (dpc != null) { - dpc.onDisplayChangedLocked(); + dpc.onDisplayChanged(); } } @@ -1305,12 +1305,23 @@ public final class DisplayManagerService extends SystemService { handleLogicalDisplayChangedLocked(display); } + private void handleLogicalDisplayDeviceStateTransitionLocked(@NonNull LogicalDisplay display) { + final int displayId = display.getDisplayIdLocked(); + final DisplayPowerController dpc = mDisplayPowerControllers.get(displayId); + if (dpc != null) { + dpc.onDeviceStateTransition(); + } + } + private Runnable updateDisplayStateLocked(DisplayDevice device) { // Blank or unblank the display immediately to match the state requested // by the display power controller (if known). DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked(); if ((info.flags & DisplayDeviceInfo.FLAG_NEVER_BLANK) == 0) { final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(device); + if (display == null) { + return null; + } final int displayId = display.getDisplayIdLocked(); final int state = mDisplayStates.get(displayId); @@ -1454,9 +1465,12 @@ public final class DisplayManagerService extends SystemService { clearViewportsLocked(); // Configure each display device. - mDisplayDeviceRepo.forEachLocked((DisplayDevice device) -> { - configureDisplayLocked(t, device); - device.performTraversalLocked(t); + mLogicalDisplayMapper.forEachLocked((LogicalDisplay display) -> { + final DisplayDevice device = display.getPrimaryDisplayDeviceLocked(); + if (device != null) { + configureDisplayLocked(t, device); + device.performTraversalLocked(t); + } }); // Tell the input system about these new viewports. @@ -2154,6 +2168,10 @@ public final class DisplayManagerService extends SystemService { case LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED: handleLogicalDisplayFrameRateOverridesChangedLocked(display); break; + + case LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_DEVICE_STATE_TRANSITION: + handleLogicalDisplayDeviceStateTransitionLocked(display); + break; } } diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 5cd0534c8e931..61705054e84c7 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -763,12 +763,20 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call * when displays get swapped on foldable devices. For example, different brightness properties * of each display need to be properly reflected in AutomaticBrightnessController. */ - public void onDisplayChangedLocked() { + public void onDisplayChanged() { // TODO: b/175821789 - Support high brightness on multiple (folding) displays - mUniqueDisplayId = mLogicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId(); } + /** + * Called when the displays are preparing to transition from one device state to another. + * This process involves turning off some displays so we need updatePowerState() to run and + * calculate the new state. + */ + public void onDeviceStateTransition() { + sendUpdatePowerState(); + } + /** * Unregisters all listeners and interrupts all running threads; halting future work. * @@ -1004,7 +1012,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mIgnoreProximityUntilChanged = false; } - if (!mLogicalDisplay.isEnabled() || mScreenOffBecauseOfProximity) { + if (!mLogicalDisplay.isEnabled() + || mLogicalDisplay.getPhase() == LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION + || mScreenOffBecauseOfProximity) { state = Display.STATE_OFF; } diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java index 15894199f806a..9acb4c8f471a8 100644 --- a/services/core/java/com/android/server/display/LogicalDisplay.java +++ b/services/core/java/com/android/server/display/LogicalDisplay.java @@ -16,6 +16,7 @@ package com.android.server.display; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.Point; @@ -65,6 +66,33 @@ import java.util.Objects; final class LogicalDisplay { private static final String TAG = "LogicalDisplay"; + /** + * Phase indicating the logical display's existence is hidden from the rest of the framework. + * This can happen if the current layout has specifically requested to keep this display + * disabled. + */ + static final int DISPLAY_PHASE_DISABLED = -1; + + /** + * Phase indicating that the logical display is going through a layout transition. + * When in this phase, other systems can choose to special case power-state handling of a + * display that might be in a transition. + */ + static final int DISPLAY_PHASE_LAYOUT_TRANSITION = 0; + + /** + * The display is exposed to the rest of the system and its power state is determined by a + * power-request from PowerManager. + */ + static final int DISPLAY_PHASE_ENABLED = 1; + + @IntDef(prefix = {"DISPLAY_PHASE" }, value = { + DISPLAY_PHASE_DISABLED, + DISPLAY_PHASE_LAYOUT_TRANSITION, + DISPLAY_PHASE_ENABLED + }) + @interface DisplayPhase {} + // 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; @@ -129,10 +157,12 @@ final class LogicalDisplay { private final Rect mTempDisplayRect = new Rect(); /** - * Indicates that the Logical display is enabled (default). See {@link #setEnabled} for - * more information. + * Indicates the current phase of the display. Generally, phases supersede any + * requests from PowerManager in DPC's calculation for the display state. Only when the + * phase is ENABLED does PowerManager's request for the display take effect. */ - private boolean mIsEnabled = true; + @DisplayPhase + private int mPhase = DISPLAY_PHASE_ENABLED; /** * The UID mappings for refresh rate override @@ -721,27 +751,32 @@ final class LogicalDisplay { return old; } - /** - * Sets the LogicalDisplay to be enabled or disabled. If the display is not enabled, - * the system will always set the display to power off, regardless of the global state of the - * device. - * TODO: b/170498827 - Remove when updateDisplayStateLocked is updated. - */ - public void setEnabled(boolean isEnabled) { - mIsEnabled = isEnabled; + public void setPhase(@DisplayPhase int phase) { + mPhase = phase; } /** - * @return {@code true} iff the LogicalDisplay is enabled or {@code false} - * if disabled indicating that the display has been forced to be OFF. + * Returns the currently set phase for this LogicalDisplay. Phases are used when transitioning + * from one device state to another. {@see LogicalDisplayMapper}. + */ + @DisplayPhase + public int getPhase() { + return mPhase; + } + + /** + * @return {@code true} if the LogicalDisplay is enabled or {@code false} + * if disabled indicating that the display should be hidden from the rest of the apps and + * framework. */ public boolean isEnabled() { - return mIsEnabled; + // DISPLAY_PHASE_LAYOUT_TRANSITION is still considered an 'enabled' phase. + return mPhase == DISPLAY_PHASE_ENABLED || mPhase == DISPLAY_PHASE_LAYOUT_TRANSITION; } public void dumpLocked(PrintWriter pw) { pw.println("mDisplayId=" + mDisplayId); - pw.println("mIsEnabled=" + mIsEnabled); + pw.println("mPhase=" + mPhase); pw.println("mLayerStack=" + mLayerStack); pw.println("mHasContent=" + mHasContent); pw.println("mDesiredDisplayModeSpecs={" + mDesiredDisplayModeSpecs + "}"); diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java index fcfa674dcc4e3..4c9a2d702114e 100644 --- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java +++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java @@ -16,18 +16,24 @@ package com.android.server.display; +import android.annotation.NonNull; +import android.content.Context; import android.hardware.devicestate.DeviceStateManager; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; 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.DisplayAddress; import android.view.DisplayInfo; +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.display.LogicalDisplay.DisplayPhase; import com.android.server.display.layout.Layout; import java.io.PrintWriter; @@ -55,11 +61,20 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { public static final int LOGICAL_DISPLAY_EVENT_REMOVED = 3; public static final int LOGICAL_DISPLAY_EVENT_SWAPPED = 4; public static final int LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED = 5; + public static final int LOGICAL_DISPLAY_EVENT_DEVICE_STATE_TRANSITION = 6; public static final int DISPLAY_GROUP_EVENT_ADDED = 1; public static final int DISPLAY_GROUP_EVENT_CHANGED = 2; public static final int DISPLAY_GROUP_EVENT_REMOVED = 3; + private static final int TIMEOUT_STATE_TRANSITION_MILLIS = 500; + + private static final int MSG_TRANSITION_TO_PENDING_DEVICE_STATE = 1; + + private static final int UPDATE_STATE_NEW = 0; + private static final int UPDATE_STATE_TRANSITION = 1; + private static final int UPDATE_STATE_UPDATED = 2; + /** * Temporary display info, used for comparing display configurations. */ @@ -75,6 +90,11 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { */ private final boolean mSingleDisplayDemoMode; + /** + * True if the device can have more than one internal display on at a time. + */ + private final boolean mSupportsConcurrentInternalDisplays; + /** * Map of all logical displays indexed by logical display id. * Any modification to mLogicalDisplays must invalidate the DisplayManagerGlobal cache. @@ -89,13 +109,16 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { private final DisplayDeviceRepository mDisplayDeviceRepo; private final DeviceStateToLayoutMap mDeviceStateToLayoutMap; private final Listener mListener; + private final DisplayManagerService.SyncRoot mSyncRoot; + private final LogicalDisplayMapperHandler mHandler; /** * 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. + * is deleted or {@link LOGICAL_DISPLAY_EVENT_CHANGED} when it is changed. The values are any + * of the {@code UPDATE_STATE_*} constant types. */ - private final SparseBooleanArray mUpdatedLogicalDisplays = new SparseBooleanArray(); + private final SparseIntArray mUpdatedLogicalDisplays = new SparseIntArray(); /** * Keeps track of all the display groups that we already told other people about. IOW, if a @@ -119,11 +142,18 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { private int mNextNonDefaultGroupId = Display.DEFAULT_DISPLAY_GROUP + 1; private Layout mCurrentLayout = null; private int mDeviceState = DeviceStateManager.INVALID_DEVICE_STATE; + private int mPendingDeviceState = DeviceStateManager.INVALID_DEVICE_STATE; - LogicalDisplayMapper(DisplayDeviceRepository repo, Listener listener) { + LogicalDisplayMapper(@NonNull Context context, @NonNull DisplayDeviceRepository repo, + @NonNull Listener listener, @NonNull DisplayManagerService.SyncRoot syncRoot, + @NonNull Handler handler) { + mSyncRoot = syncRoot; + mHandler = new LogicalDisplayMapperHandler(handler.getLooper()); mDisplayDeviceRepo = repo; mListener = listener; mSingleDisplayDemoMode = SystemProperties.getBoolean("persist.demo.singledisplay", false); + mSupportsConcurrentInternalDisplays = context.getResources().getBoolean( + com.android.internal.R.bool.config_supportsConcurrentInternalDisplays); mDisplayDeviceRepo.addListener(this); mDeviceStateToLayoutMap = new DeviceStateToLayoutMap(); } @@ -142,6 +172,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { if (DEBUG) { Slog.d(TAG, "Display device changed: " + device.getDisplayDeviceInfoLocked()); } + finishStateTransitionLocked(false /*force*/); updateLogicalDisplaysLocked(); break; @@ -166,7 +197,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { public LogicalDisplay getDisplayLocked(DisplayDevice device) { final int count = mLogicalDisplays.size(); for (int i = 0; i < count; i++) { - LogicalDisplay display = mLogicalDisplays.valueAt(i); + final LogicalDisplay display = mLogicalDisplays.valueAt(i); if (display.getPrimaryDisplayDeviceLocked() == device) { return display; } @@ -198,6 +229,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { } } + @VisibleForTesting public int getDisplayGroupIdFromDisplayIdLocked(int displayId) { final LogicalDisplay display = getDisplayLocked(displayId); if (display == null) { @@ -225,7 +257,6 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { ipw.increaseIndent(); ipw.println("mSingleDisplayDemoMode=" + mSingleDisplayDemoMode); - ipw.println("mCurrentLayout=" + mCurrentLayout); final int logicalDisplayCount = mLogicalDisplays.size(); @@ -244,19 +275,78 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { } void setDeviceStateLocked(int state) { - if (state != mDeviceState) { - resetLayoutLocked(); - mDeviceState = state; - applyLayoutLocked(); - updateLogicalDisplaysLocked(); + Slog.i(TAG, "Requesting Transition to state: " + state); + // As part of a state transition, we may need to turn off some displays temporarily so that + // the transition is smooth. Plus, on some devices, only one internal displays can be + // on at a time. We use DISPLAY_PHASE_LAYOUT_TRANSITION to mark a display that needs to be + // temporarily turned off. + if (mDeviceState != DeviceStateManager.INVALID_DEVICE_STATE) { + resetLayoutLocked(mDeviceState, state, LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION); + } + mPendingDeviceState = state; + if (areAllTransitioningDisplaysOffLocked()) { + // Nothing to wait on, we're good to go + transitionToPendingStateLocked(); + return; + } + + if (DEBUG) { + Slog.d(TAG, "Postponing transition to state: " + mPendingDeviceState); + } + // Send the transitioning phase updates to DisplayManager so that the displays can + // start turning OFF in preparation for the new layout. + updateLogicalDisplaysLocked(); + mHandler.sendEmptyMessageDelayed(MSG_TRANSITION_TO_PENDING_DEVICE_STATE, + TIMEOUT_STATE_TRANSITION_MILLIS); + } + + private boolean areAllTransitioningDisplaysOffLocked() { + final int count = mLogicalDisplays.size(); + for (int i = 0; i < count; i++) { + final LogicalDisplay display = mLogicalDisplays.valueAt(i); + if (display.getPhase() != LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION) { + continue; + } + + final DisplayDevice device = display.getPrimaryDisplayDeviceLocked(); + if (device != null) { + final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked(); + if (info.state != Display.STATE_OFF) { + return false; + } + } + } + return true; + } + + private void transitionToPendingStateLocked() { + resetLayoutLocked(mDeviceState, mPendingDeviceState, LogicalDisplay.DISPLAY_PHASE_ENABLED); + mDeviceState = mPendingDeviceState; + mPendingDeviceState = DeviceStateManager.INVALID_DEVICE_STATE; + applyLayoutLocked(); + updateLogicalDisplaysLocked(); + } + + private void finishStateTransitionLocked(boolean force) { + if (mPendingDeviceState == DeviceStateManager.INVALID_DEVICE_STATE) { + return; + } + + final boolean displaysOff = areAllTransitioningDisplaysOffLocked(); + if (displaysOff || force) { + transitionToPendingStateLocked(); + mHandler.removeMessages(MSG_TRANSITION_TO_PENDING_DEVICE_STATE); + } else if (DEBUG) { + Slog.d(TAG, "Not yet ready to transition to state=" + mPendingDeviceState + + " with displays-off=" + displaysOff + " and force=" + force); } } private void handleDisplayDeviceAddedLocked(DisplayDevice device) { DisplayDeviceInfo deviceInfo = device.getDisplayDeviceInfoLocked(); // 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 + // This initializes a default dynamic display layout for INTERNAL + // devices, which is used as a fallback in case no static layout definitions // exist or cannot be loaded. if (deviceInfo.type == Display.TYPE_INTERNAL) { initializeInternalDisplayDeviceLocked(device); @@ -289,7 +379,8 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { display.updateLocked(mDisplayDeviceRepo); final DisplayInfo newDisplayInfo = display.getDisplayInfoLocked(); - final boolean wasPreviouslyUpdated = mUpdatedLogicalDisplays.get(displayId); + final int updateState = mUpdatedLogicalDisplays.get(displayId, UPDATE_STATE_NEW); + final boolean wasPreviouslyUpdated = updateState != UPDATE_STATE_NEW; // The display is no longer valid and needs to be removed. if (!display.isValidLocked()) { @@ -331,6 +422,10 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { assignDisplayGroupLocked(display); mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_CHANGED); + } else if (updateState == UPDATE_STATE_TRANSITION) { + mLogicalDisplaysToUpdate.put(displayId, + LOGICAL_DISPLAY_EVENT_DEVICE_STATE_TRANSITION); + // Display frame rate overrides changed. } else if (!display.getPendingFrameRateOverrideUids().isEmpty()) { mLogicalDisplaysToUpdate.put( @@ -347,7 +442,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { } } - mUpdatedLogicalDisplays.put(displayId, true); + mUpdatedLogicalDisplays.put(displayId, UPDATE_STATE_UPDATED); } // Go through the groups and do the same thing. We do this after displays since group @@ -376,12 +471,13 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { // 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. + sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_DEVICE_STATE_TRANSITION); 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); + sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_ADDED); sendUpdatesForGroupsLocked(DISPLAY_GROUP_EVENT_CHANGED); sendUpdatesForGroupsLocked(DISPLAY_GROUP_EVENT_REMOVED); @@ -400,7 +496,14 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { } final int id = mLogicalDisplaysToUpdate.keyAt(i); - mListener.onLogicalDisplayEventLocked(getDisplayLocked(id), msg); + final LogicalDisplay display = getDisplayLocked(id); + if (DEBUG) { + final DisplayDevice device = display.getPrimaryDisplayDeviceLocked(); + final String uniqueId = device == null ? "null" : device.getUniqueId(); + Slog.d(TAG, "Sending " + displayEventToString(msg) + " for display=" + id + + " with device=" + uniqueId); + } + mListener.onLogicalDisplayEventLocked(display, msg); if (msg == LOGICAL_DISPLAY_EVENT_REMOVED) { // We wait until we sent the EVENT_REMOVED event before actually removing the // display. @@ -464,36 +567,81 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { } /** - * Resets the current layout in preparation for a new layout. Layouts can specify if some - * displays should be disabled (OFF). When switching from one layout to another, we go - * through each of the displays and make sure any displays we might have disabled are - * enabled again. + * Goes through all the displays used in the layouts for the specified {@code fromState} and + * {@code toState} and applies the specified {@code phase}. When a new layout is requested, we + * put the displays that will change into a transitional phase so that they can all be turned + * OFF. Once all are confirmed OFF, then this method gets called again to reset the phase to + * normal operation. This helps to ensure that all display-OFF requests are made before + * display-ON which in turn hides any resizing-jank windows might incur when switching displays. + * + * @param fromState The state we are switching from. + * @param toState The state we are switching to. + * @param phase The new phase to apply to the displays. */ - 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, true); // Reset all displays back to enabled + private void resetLayoutLocked(int fromState, int toState, @DisplayPhase int phase) { + final Layout fromLayout = mDeviceStateToLayoutMap.get(fromState); + final Layout toLayout = mDeviceStateToLayoutMap.get(toState); + + final int count = mLogicalDisplays.size(); + for (int i = 0; i < count; i++) { + final LogicalDisplay logicalDisplay = mLogicalDisplays.valueAt(i); + final int displayId = logicalDisplay.getDisplayIdLocked(); + final DisplayDevice device = logicalDisplay.getPrimaryDisplayDeviceLocked(); + if (device == null) { + // If there's no device, then the logical display is due to be removed. Ignore it. + continue; + } + + // Grab the display associations this display-device has in the old layout and the + // new layout. + final DisplayAddress address = device.getDisplayDeviceInfoLocked().address; + + // Virtual displays do not have addresses. + final Layout.Display fromDisplay = + address != null ? fromLayout.getByAddress(address) : null; + final Layout.Display toDisplay = + address != null ? toLayout.getByAddress(address) : null; + + // If a layout doesn't mention a display-device at all, then the display-device defaults + // to enabled. This is why we treat null as "enabled" in the code below. + final boolean wasEnabled = fromDisplay == null || fromDisplay.isEnabled(); + final boolean willBeEnabled = toDisplay == null || toDisplay.isEnabled(); + + final boolean deviceHasNewLogicalDisplayId = fromDisplay != null && toDisplay != null + && fromDisplay.getLogicalDisplayId() != toDisplay.getLogicalDisplayId(); + + // We consider a display-device as changing/transition if + // 1) It's already marked as transitioning + // 2) It's going from enabled to disabled + // 3) It's enabled, but it's mapped to a new logical display ID. To the user this + // would look like apps moving from one screen to another since task-stacks stay + // with the logical display [ID]. + final boolean isTransitioning = + (logicalDisplay.getPhase() == LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION) + || (wasEnabled && !willBeEnabled) + || (wasEnabled && deviceHasNewLogicalDisplayId); + + if (isTransitioning) { + setDisplayPhase(logicalDisplay, phase); + if (phase == LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION) { + mUpdatedLogicalDisplays.put(displayId, UPDATE_STATE_TRANSITION); + } } } } - /** * 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); + final Layout oldLayout = mCurrentLayout; + mCurrentLayout = mDeviceStateToLayoutMap.get(mDeviceState); + Slog.i(TAG, "Applying layout: " + mCurrentLayout + ", Previous layout: " + oldLayout); // Go through each of the displays in the current layout set. - final int size = layout.size(); + final int size = mCurrentLayout.size(); for (int i = 0; i < size; i++) { - final Layout.Display displayLayout = layout.getAt(i); + final Layout.Display displayLayout = mCurrentLayout.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 @@ -521,8 +669,12 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { if (newDisplay != oldDisplay) { newDisplay.swapDisplaysLocked(oldDisplay); } - enableDisplayLocked(newDisplay, displayLayout.isEnabled()); + + if (!displayLayout.isEnabled()) { + setDisplayPhase(newDisplay, LogicalDisplay.DISPLAY_PHASE_DISABLED); + } } + } @@ -540,23 +692,23 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { final LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device); display.updateLocked(mDisplayDeviceRepo); mLogicalDisplays.put(displayId, display); - enableDisplayLocked(display, device != null); + setDisplayPhase(display, LogicalDisplay.DISPLAY_PHASE_ENABLED); return display; } - private void enableDisplayLocked(LogicalDisplay display, boolean isEnabled) { + private void setDisplayPhase(LogicalDisplay display, @DisplayPhase int phase) { final int displayId = display.getDisplayIdLocked(); final DisplayInfo info = display.getDisplayInfoLocked(); final boolean disallowSecondaryDisplay = mSingleDisplayDemoMode && (info.type != Display.TYPE_INTERNAL); - if (isEnabled && disallowSecondaryDisplay) { + if (phase != LogicalDisplay.DISPLAY_PHASE_DISABLED && disallowSecondaryDisplay) { Slog.i(TAG, "Not creating a logical display for a secondary display because single" + " display demo mode is enabled: " + display.getDisplayInfoLocked()); - isEnabled = false; + phase = LogicalDisplay.DISPLAY_PHASE_DISABLED; } - display.setEnabled(isEnabled); + display.setPhase(phase); } private int assignDisplayGroupIdLocked(boolean isOwnDisplayGroup) { @@ -564,14 +716,15 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { } private void initializeInternalDisplayDeviceLocked(DisplayDevice device) { - // We always want to make sure that our default display layout creates a logical + // We always want to make sure that our default 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); + // the default layout definition if it is not already there. + final Layout layout = 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, true /* isEnabled */); + final boolean isEnabled = isDefault || mSupportsConcurrentInternalDisplays; + layout.createDisplayLocked(info.address, isDefault, isEnabled); } private int assignLayerStackLocked(int displayId) { @@ -580,9 +733,45 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { return displayId; } + private String displayEventToString(int msg) { + switch(msg) { + case LOGICAL_DISPLAY_EVENT_ADDED: + return "added"; + case LOGICAL_DISPLAY_EVENT_DEVICE_STATE_TRANSITION: + return "transition"; + case LOGICAL_DISPLAY_EVENT_CHANGED: + return "changed"; + case LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED: + return "framerate_override"; + case LOGICAL_DISPLAY_EVENT_SWAPPED: + return "swapped"; + case LOGICAL_DISPLAY_EVENT_REMOVED: + return "removed"; + } + return null; + } + public interface Listener { void onLogicalDisplayEventLocked(LogicalDisplay display, int event); void onDisplayGroupEventLocked(int groupId, int event); void onTraversalRequested(); } + + private class LogicalDisplayMapperHandler extends Handler { + LogicalDisplayMapperHandler(Looper looper) { + super(looper, null, true /*async*/); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_TRANSITION_TO_PENDING_DEVICE_STATE: + synchronized (mSyncRoot) { + finishStateTransitionLocked(true /*force*/); + } + break; + } + } + } + } diff --git a/services/core/java/com/android/server/display/layout/Layout.java b/services/core/java/com/android/server/display/layout/Layout.java index ef336674df5d5..e53aec12f1867 100644 --- a/services/core/java/com/android/server/display/layout/Layout.java +++ b/services/core/java/com/android/server/display/layout/Layout.java @@ -19,6 +19,7 @@ package com.android.server.display.layout; import static android.view.Display.DEFAULT_DISPLAY; import android.annotation.NonNull; +import android.annotation.Nullable; import android.util.Slog; import android.view.DisplayAddress; @@ -100,11 +101,28 @@ public class Layout { * * @return The display corresponding to the specified display ID. */ + @Nullable public Display getById(int id) { for (int i = 0; i < mDisplays.size(); i++) { - Display layout = mDisplays.get(i); - if (id == layout.getLogicalDisplayId()) { - return layout; + Display display = mDisplays.get(i); + if (id == display.getLogicalDisplayId()) { + return display; + } + } + return null; + } + + /** + * @param address The display address to check. + * + * @return The display corresponding to the specified address. + */ + @Nullable + public Display getByAddress(@NonNull DisplayAddress address) { + for (int i = 0; i < mDisplays.size(); i++) { + Display display = mDisplays.get(i); + if (address.equals(display.getAddress())) { + return display; } } return null; diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java index d784a221fbd7b..8279624f6b970 100644 --- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java +++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java @@ -27,17 +27,20 @@ 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 static org.mockito.Mockito.when; import android.app.PropertyInvalidatedCache; import android.content.Context; +import android.content.res.Resources; +import android.os.Handler; import android.os.Parcel; import android.os.Process; +import android.os.test.TestLooper; 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; @@ -61,9 +64,12 @@ public class LogicalDisplayMapperTest { private DisplayDeviceRepository mDisplayDeviceRepo; private LogicalDisplayMapper mLogicalDisplayMapper; - private Context mContext; + private TestLooper mLooper; + private Handler mHandler; @Mock LogicalDisplayMapper.Listener mListenerMock; + @Mock Context mContextMock; + @Mock Resources mResourcesMock; @Captor ArgumentCaptor mDisplayCaptor; @@ -73,7 +79,6 @@ public class LogicalDisplayMapperTest { System.setProperty("dexmaker.share_classloader", "true"); MockitoAnnotations.initMocks(this); - mContext = InstrumentationRegistry.getContext(); mDisplayDeviceRepo = new DisplayDeviceRepository( new DisplayManagerService.SyncRoot(), new PersistentDataStore(new PersistentDataStore.Injector() { @@ -94,7 +99,15 @@ public class LogicalDisplayMapperTest { // Disable binder caches in this process. PropertyInvalidatedCache.disableForTestMode(); - mLogicalDisplayMapper = new LogicalDisplayMapper(mDisplayDeviceRepo, mListenerMock); + when(mContextMock.getResources()).thenReturn(mResourcesMock); + when(mResourcesMock.getBoolean( + com.android.internal.R.bool.config_supportsConcurrentInternalDisplays)) + .thenReturn(true); + + mLooper = new TestLooper(); + mHandler = new Handler(mLooper.getLooper()); + mLogicalDisplayMapper = new LogicalDisplayMapper(mContextMock, mDisplayDeviceRepo, + mListenerMock, new DisplayManagerService.SyncRoot(), mHandler); } @@ -299,7 +312,7 @@ public class LogicalDisplayMapperTest { private DisplayDeviceInfo mSentInfo; TestDisplayDevice() { - super(null, null, "test_display_" + sUniqueTestDisplayId++, mContext); + super(null, null, "test_display_" + sUniqueTestDisplayId++, mContextMock); mInfo = new DisplayDeviceInfo(); }