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();
}