diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index 8bc11cbc61de9..f94e0313771e6 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -559,18 +559,20 @@ public final class DisplayManager { * @see #DISPLAY_CATEGORY_PRESENTATION */ public Display[] getDisplays(String category) { - final int[] displayIds = mGlobal.getDisplayIds(); + boolean includeDisabled = (category != null + && category.equals(DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)); + final int[] displayIds = mGlobal.getDisplayIds(includeDisabled); synchronized (mLock) { try { - if (category == null - || DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED.equals(category)) { - addAllDisplaysLocked(mTempDisplays, displayIds); - } else if (category.equals(DISPLAY_CATEGORY_PRESENTATION)) { + if (DISPLAY_CATEGORY_PRESENTATION.equals(category)) { addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_WIFI); addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_EXTERNAL); addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_OVERLAY); addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_VIRTUAL); addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_INTERNAL); + } else if (category == null + || DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED.equals(category)) { + addAllDisplaysLocked(mTempDisplays, displayIds); } return mTempDisplays.toArray(new Display[mTempDisplays.size()]); } finally { diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java index 74356ddecc76d..63dc7c7ed6615 100644 --- a/core/java/android/hardware/display/DisplayManagerGlobal.java +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -206,6 +206,16 @@ public final class DisplayManagerGlobal { */ @UnsupportedAppUsage public int[] getDisplayIds() { + return getDisplayIds(/* includeDisabled= */ false); + } + + /** + * Gets all currently valid logical display ids. + * + * @param includeDisabled True if the returned list of displays includes disabled displays. + * @return An array containing all display ids. + */ + public int[] getDisplayIds(boolean includeDisabled) { try { synchronized (mLock) { if (USE_CACHE) { @@ -214,7 +224,7 @@ public final class DisplayManagerGlobal { } } - int[] displayIds = mDm.getDisplayIds(); + int[] displayIds = mDm.getDisplayIds(includeDisabled); if (USE_CACHE) { mDisplayIdCache = displayIds; } diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl index ca3e580944005..a4115d178f6f0 100644 --- a/core/java/android/hardware/display/IDisplayManager.aidl +++ b/core/java/android/hardware/display/IDisplayManager.aidl @@ -36,7 +36,7 @@ import android.view.Surface; interface IDisplayManager { @UnsupportedAppUsage DisplayInfo getDisplayInfo(int displayId); - int[] getDisplayIds(); + int[] getDisplayIds(boolean includeDisabled); boolean isUidPresentOnDisplay(int uid, int displayId); diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 1b20e6a47cb82..f9c8f064de968 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -1545,7 +1545,7 @@ public final class DisplayManagerService extends SystemService { mSyncRoot.notifyAll(); } - sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_ADDED); + sendDisplayEventLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_ADDED); Runnable work = updateDisplayStateLocked(device); if (work != null) { @@ -1564,7 +1564,7 @@ public final class DisplayManagerService extends SystemService { // We don't bother invalidating the display info caches here because any changes to the // display info will trigger a cache invalidation inside of LogicalDisplay before we hit // this point. - sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED); + sendDisplayEventLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED); scheduleTraversalLocked(false); mPersistentDataStore.saveIfNeeded(); @@ -1593,7 +1593,7 @@ public final class DisplayManagerService extends SystemService { mDisplayStates.delete(displayId); mDisplayBrightnesses.delete(displayId); DisplayManagerGlobal.invalidateLocalDisplayInfoCaches(); - sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED); + sendDisplayEventLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED); scheduleTraversalLocked(false); if (mDisplayWindowPolicyControllers.contains(displayId)) { @@ -1609,24 +1609,13 @@ public final class DisplayManagerService extends SystemService { } private void handleLogicalDisplaySwappedLocked(@NonNull LogicalDisplay display) { - final DisplayDevice device = display.getPrimaryDisplayDeviceLocked(); - final Runnable work = updateDisplayStateLocked(device); - if (work != null) { - mHandler.post(work); - } - final int displayId = display.getDisplayIdLocked(); + handleLogicalDisplayChangedLocked(display); + final int displayId = display.getDisplayIdLocked(); if (displayId == Display.DEFAULT_DISPLAY) { notifyDefaultDisplayDeviceUpdated(display); } - - DisplayPowerController dpc = mDisplayPowerControllers.get(displayId); - if (dpc != null) { - dpc.onDisplayChanged(); - } - mPersistentDataStore.saveIfNeeded(); mHandler.sendEmptyMessage(MSG_LOAD_BRIGHTNESS_CONFIGURATIONS); - handleLogicalDisplayChangedLocked(display); } private void notifyDefaultDisplayDeviceUpdated(LogicalDisplay display) { @@ -1638,7 +1627,7 @@ public final class DisplayManagerService extends SystemService { final int displayId = display.getDisplayIdLocked(); final DisplayPowerController dpc = mDisplayPowerControllers.get(displayId); if (dpc != null) { - dpc.onDeviceStateTransition(); + dpc.onDisplayChanged(); } } @@ -2348,9 +2337,13 @@ public final class DisplayManagerService extends SystemService { } } - private void sendDisplayEventLocked(int displayId, @DisplayEvent int event) { - Message msg = mHandler.obtainMessage(MSG_DELIVER_DISPLAY_EVENT, displayId, event); - mHandler.sendMessage(msg); + private void sendDisplayEventLocked(@NonNull LogicalDisplay display, @DisplayEvent int event) { + // Only send updates outside of DisplayManagerService for enabled displays + if (display.isEnabledLocked()) { + int displayId = display.getDisplayIdLocked(); + Message msg = mHandler.obtainMessage(MSG_DELIVER_DISPLAY_EVENT, displayId, event); + mHandler.sendMessage(msg); + } } private void sendDisplayGroupEvent(int groupId, int event) { @@ -2636,8 +2629,7 @@ public final class DisplayManagerService extends SystemService { } private void handleBrightnessChange(LogicalDisplay display) { - sendDisplayEventLocked(display.getDisplayIdLocked(), - DisplayManagerGlobal.EVENT_DISPLAY_BRIGHTNESS_CHANGED); + sendDisplayEventLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_BRIGHTNESS_CHANGED); } private DisplayDevice getDeviceForDisplayLocked(int displayId) { @@ -2854,12 +2846,12 @@ public final class DisplayManagerService extends SystemService { * Returns the list of all display ids. */ @Override // Binder call - public int[] getDisplayIds() { + public int[] getDisplayIds(boolean includeDisabled) { final int callingUid = Binder.getCallingUid(); final long token = Binder.clearCallingIdentity(); try { synchronized (mSyncRoot) { - return mLogicalDisplayMapper.getDisplayIdsLocked(callingUid); + return mLogicalDisplayMapper.getDisplayIdsLocked(callingUid, includeDisabled); } } finally { Binder.restoreCallingIdentity(token); @@ -3337,6 +3329,11 @@ public final class DisplayManagerService extends SystemService { final long token = Binder.clearCallingIdentity(); try { synchronized (mSyncRoot) { + LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked( + displayId, /* includeDisabled= */ false); + if (display == null || !display.isEnabledLocked()) { + return null; + } DisplayPowerController dpc = mDisplayPowerControllers.get(displayId); if (dpc != null) { return dpc.getBrightnessInfo(); diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java index c131ed62c6b2b..ecae8330d5321 100644 --- a/services/core/java/com/android/server/display/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/DisplayModeDirector.java @@ -16,6 +16,7 @@ package com.android.server.display; +import static android.hardware.display.DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED; import static android.hardware.display.DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE; import static android.os.PowerManager.BRIGHTNESS_INVALID; @@ -1457,7 +1458,7 @@ public class DisplayModeDirector { SparseArray modes = new SparseArray<>(); SparseArray defaultModes = new SparseArray<>(); DisplayInfo info = new DisplayInfo(); - Display[] displays = dm.getDisplays(); + Display[] displays = dm.getDisplays(DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED); for (Display d : displays) { final int displayId = d.getDisplayId(); d.getDisplayInfo(info); @@ -2332,7 +2333,8 @@ public class DisplayModeDirector { sensorManager.addProximityActiveListener(BackgroundThread.getExecutor(), this); synchronized (mSensorObserverLock) { - for (Display d : mDisplayManager.getDisplays()) { + for (Display d : mDisplayManager.getDisplays( + DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)) { mDozeStateByDisplay.put(d.getDisplayId(), mInjector.isDozeState(d)); } } @@ -2343,7 +2345,8 @@ public class DisplayModeDirector { } private void recalculateVotesLocked() { - final Display[] displays = mDisplayManager.getDisplays(); + final Display[] displays = mDisplayManager.getDisplays( + DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED); for (Display d : displays) { int displayId = d.getDisplayId(); Vote vote = null; diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index c426e69332c7d..d7bbb109b6f71 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -491,6 +491,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private final String mSuspendBlockerIdProxNegative; private final String mSuspendBlockerIdProxDebounce; + private boolean mIsEnabled; + private boolean mIsInTransition; + /** * Creates the display power controller. */ @@ -512,6 +515,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mDisplayDevice = mLogicalDisplay.getPrimaryDisplayDeviceLocked(); mUniqueDisplayId = logicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId(); mDisplayStatsId = mUniqueDisplayId.hashCode(); + mIsEnabled = logicalDisplay.isEnabledLocked(); + mIsInTransition = logicalDisplay.isInTransitionLocked(); mHandler = new DisplayControllerHandler(handler.getLooper()); mLastBrightnessEvent = new BrightnessEvent(mDisplayId); mTempBrightnessEvent = new BrightnessEvent(mDisplayId); @@ -789,13 +794,30 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call final DisplayDeviceConfig config = device.getDisplayDeviceConfig(); final IBinder token = device.getDisplayTokenLocked(); final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked(); + final boolean isEnabled = mLogicalDisplay.isEnabledLocked(); + final boolean isInTransition = mLogicalDisplay.isInTransitionLocked(); mHandler.post(() -> { + boolean changed = false; if (mDisplayDevice != device) { + changed = true; mDisplayDevice = device; mUniqueDisplayId = uniqueId; mDisplayStatsId = mUniqueDisplayId.hashCode(); mDisplayDeviceConfig = config; loadFromDisplayDeviceConfig(token, info); + + // Since the underlying display-device changed, we really don't know the + // last command that was sent to change it's state. Lets assume it is unknown so + // that we trigger a change immediately. + mPowerState.resetScreenState(); + } + if (mIsEnabled != isEnabled || mIsInTransition != isInTransition) { + changed = true; + mIsEnabled = isEnabled; + mIsInTransition = isInTransition; + } + + if (changed) { if (DEBUG) { Trace.beginAsyncSection("DisplayPowerController#updatePowerState", 0); } @@ -807,15 +829,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call }); } - /** - * 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. * @@ -1291,8 +1304,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mIgnoreProximityUntilChanged = false; } - if (!mLogicalDisplay.isEnabled() - || mLogicalDisplay.getPhase() == LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION + if (!mIsEnabled + || mIsInTransition || mScreenOffBecauseOfProximity) { state = Display.STATE_OFF; } diff --git a/services/core/java/com/android/server/display/DisplayPowerState.java b/services/core/java/com/android/server/display/DisplayPowerState.java index 2f22d33f552aa..f650b118b8159 100644 --- a/services/core/java/com/android/server/display/DisplayPowerState.java +++ b/services/core/java/com/android/server/display/DisplayPowerState.java @@ -145,7 +145,7 @@ final class DisplayPowerState { public void setScreenState(int state) { if (mScreenState != state) { if (DEBUG) { - Slog.d(TAG, "setScreenState: state=" + state); + Slog.w(TAG, "setScreenState: state=" + Display.stateToString(state)); } mScreenState = state; @@ -339,6 +339,15 @@ final class DisplayPowerState { if (mColorFade != null) mColorFade.dump(pw); } + /** + * Resets the screen state to unknown. Useful when the underlying display-device changes for the + * LogicalDisplay and we do not know the last state that was sent to it. + */ + void resetScreenState() { + mScreenState = Display.STATE_UNKNOWN; + mScreenReady = false; + } + private void scheduleScreenUpdate() { if (!mScreenUpdatePending) { mScreenUpdatePending = true; diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java index d14902eaf8f58..e6f27c1b0dd95 100644 --- a/services/core/java/com/android/server/display/LogicalDisplay.java +++ b/services/core/java/com/android/server/display/LogicalDisplay.java @@ -18,7 +18,6 @@ package com.android.server.display; import static com.android.server.display.DisplayDeviceInfo.TOUCH_NONE; -import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.Point; @@ -68,33 +67,6 @@ 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; @@ -158,14 +130,6 @@ final class LogicalDisplay { private final Rect mTempLayerStackRect = new Rect(); private final Rect mTempDisplayRect = new Rect(); - /** - * 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. - */ - @DisplayPhase - private int mPhase = DISPLAY_PHASE_ENABLED; - /** * The UID mappings for refresh rate override */ @@ -181,12 +145,22 @@ final class LogicalDisplay { */ private final SparseArray mTempFrameRateOverride; + // Indicates the display is enabled (allowed to be ON). + private boolean mIsEnabled; + + // Indicates the display is part of a transition from one device-state ({@link + // DeviceStateManager}) to another. Being a "part" of a transition means that either + // the {@link mIsEnabled} is changing, or the underlying mPrimiaryDisplayDevice is changing. + private boolean mIsInTransition; + public LogicalDisplay(int displayId, int layerStack, DisplayDevice primaryDisplayDevice) { mDisplayId = displayId; mLayerStack = layerStack; mPrimaryDisplayDevice = primaryDisplayDevice; mPendingFrameRateOverrideUids = new ArraySet<>(); mTempFrameRateOverride = new SparseArray<>(); + mIsEnabled = true; + mIsInTransition = false; } /** @@ -525,7 +499,7 @@ final class LogicalDisplay { // Prevent displays that are disabled from receiving input. // TODO(b/188914255): Remove once input can dispatch against device vs layerstack. device.setDisplayFlagsLocked(t, - (isEnabled() && device.getDisplayDeviceInfoLocked().touch != TOUCH_NONE) + (isEnabledLocked() && device.getDisplayDeviceInfoLocked().touch != TOUCH_NONE) ? SurfaceControl.DISPLAY_RECEIVES_INPUT : 0); @@ -767,32 +741,45 @@ final class LogicalDisplay { return old; } - public void setPhase(@DisplayPhase int phase) { - mPhase = phase; - } - - /** - * 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() { - // DISPLAY_PHASE_LAYOUT_TRANSITION is still considered an 'enabled' phase. - return mPhase == DISPLAY_PHASE_ENABLED || mPhase == DISPLAY_PHASE_LAYOUT_TRANSITION; + public boolean isEnabledLocked() { + return mIsEnabled; + } + + /** + * Sets the display as enabled. + * + * @param enable True if enabled, false otherwise. + */ + public void setEnabledLocked(boolean enabled) { + mIsEnabled = enabled; + } + + /** + * @return {@code true} if the LogicalDisplay is in a transition phase. This is used to indicate + * that we are getting ready to swap the underlying display-device and the display should be + * rendered appropriately to reduce jank. + */ + public boolean isInTransitionLocked() { + return mIsInTransition; + } + + /** + * Sets the transition phase. + * @param isInTransition True if it display is in transition. + */ + public void setIsInTransitionLocked(boolean isInTransition) { + mIsInTransition = isInTransition; } public void dumpLocked(PrintWriter pw) { pw.println("mDisplayId=" + mDisplayId); - pw.println("mPhase=" + mPhase); + pw.println("mIsEnabled=" + mIsEnabled); + pw.println("mIsInTransition=" + mIsInTransition); 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 70c9e23c6af81..778e418204333 100644 --- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java +++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java @@ -39,7 +39,6 @@ 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; @@ -167,6 +166,12 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { LogicalDisplayMapper(@NonNull Context context, @NonNull DisplayDeviceRepository repo, @NonNull Listener listener, @NonNull DisplayManagerService.SyncRoot syncRoot, @NonNull Handler handler) { + this(context, repo, listener, syncRoot, handler, new DeviceStateToLayoutMap()); + } + + LogicalDisplayMapper(@NonNull Context context, @NonNull DisplayDeviceRepository repo, + @NonNull Listener listener, @NonNull DisplayManagerService.SyncRoot syncRoot, + @NonNull Handler handler, @NonNull DeviceStateToLayoutMap deviceStateToLayoutMap) { mSyncRoot = syncRoot; mPowerManager = context.getSystemService(PowerManager.class); mInteractive = mPowerManager.isInteractive(); @@ -181,7 +186,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { mDeviceStatesOnWhichToSleep = toSparseBooleanArray(context.getResources().getIntArray( com.android.internal.R.array.config_deviceStatesOnWhichToSleep)); mDisplayDeviceRepo.addListener(this); - mDeviceStateToLayoutMap = new DeviceStateToLayoutMap(); + mDeviceStateToLayoutMap = deviceStateToLayoutMap; } @Override @@ -218,10 +223,22 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { } public LogicalDisplay getDisplayLocked(int displayId) { - return mLogicalDisplays.get(displayId); + return getDisplayLocked(displayId, /* includeDisabled= */ true); + } + + public LogicalDisplay getDisplayLocked(int displayId, boolean includeDisabled) { + LogicalDisplay display = mLogicalDisplays.get(displayId); + if (display == null || display.isEnabledLocked() || includeDisabled) { + return display; + } + return null; } public LogicalDisplay getDisplayLocked(DisplayDevice device) { + return getDisplayLocked(device, /* includeDisabled= */ true); + } + + public LogicalDisplay getDisplayLocked(DisplayDevice device, boolean includeDisabled) { if (device == null) { return null; } @@ -229,21 +246,26 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { for (int i = 0; i < count; i++) { final LogicalDisplay display = mLogicalDisplays.valueAt(i); if (display.getPrimaryDisplayDeviceLocked() == device) { - return display; + if (display.isEnabledLocked() || includeDisabled) { + return display; + } + return null; } } return null; } - public int[] getDisplayIdsLocked(int callingUid) { + public int[] getDisplayIdsLocked(int callingUid, boolean includeDisabled) { final int count = mLogicalDisplays.size(); int[] displayIds = new int[count]; int n = 0; for (int i = 0; i < count; i++) { LogicalDisplay display = mLogicalDisplays.valueAt(i); - DisplayInfo info = display.getDisplayInfoLocked(); - if (info.hasAccess(callingUid)) { - displayIds[n++] = mLogicalDisplays.keyAt(i); + if (display.isEnabledLocked() || includeDisabled) { + DisplayInfo info = display.getDisplayInfoLocked(); + if (info.hasAccess(callingUid)) { + displayIds[n++] = mLogicalDisplays.keyAt(i); + } } } if (n != count) { @@ -364,14 +386,12 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { void setDeviceStateLocked(int state, boolean isOverrideActive) { Slog.i(TAG, "Requesting Transition to state: " + state + ", from state=" + mDeviceState - + ", interactive=" + mInteractive); + + ", interactive=" + mInteractive + ", mBootCompleted=" + mBootCompleted); // 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 + // on at a time. We use LogicalDisplay.setIsInTransition 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); - } + resetLayoutLocked(mDeviceState, state, /* isStateChangeStarting= */ true); mPendingDeviceState = state; final boolean wakeDevice = shouldDeviceBeWoken(mPendingDeviceState, mDeviceState, mInteractive, mBootCompleted); @@ -481,7 +501,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { 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) { + if (!display.isInTransitionLocked()) { continue; } @@ -497,7 +517,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { } private void transitionToPendingStateLocked() { - resetLayoutLocked(mDeviceState, mPendingDeviceState, LogicalDisplay.DISPLAY_PHASE_ENABLED); + resetLayoutLocked(mDeviceState, mPendingDeviceState, /* isStateChangeStarting= */ false); mDeviceState = mPendingDeviceState; mPendingDeviceState = DeviceStateManager.INVALID_DEVICE_STATE; applyLayoutLocked(); @@ -789,17 +809,17 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { /** * 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 + * {@code toState} and un/marks them for transition. When a new layout is requested, we + * mark 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 transition + * marker. 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. + * @param isStateChangeStarting Indicates whether to start or end Transition phase. */ - private void resetLayoutLocked(int fromState, int toState, @DisplayPhase int phase) { + private void resetLayoutLocked(int fromState, int toState, boolean isStateChangeStarting) { final Layout fromLayout = mDeviceStateToLayoutMap.get(fromState); final Layout toLayout = mDeviceStateToLayoutMap.get(toState); @@ -817,12 +837,16 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { // new layout. final DisplayAddress address = device.getDisplayDeviceInfoLocked().address; - // Virtual displays do not have addresses. + // Virtual displays do not have addresses, so account for nulls. final Layout.Display fromDisplay = address != null ? fromLayout.getByAddress(address) : null; final Layout.Display toDisplay = address != null ? toLayout.getByAddress(address) : null; + // If the display is in one of the layouts but not the other, then the content will + // change, so in this case we also want to blank the displays to avoid jank. + final boolean displayNotInBothLayouts = (fromDisplay == null) != (toDisplay == 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(); @@ -837,16 +861,23 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { // 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]. + // 4) It's in one layout but not the other, so the content will change. final boolean isTransitioning = - (logicalDisplay.getPhase() == LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION) + logicalDisplay.isInTransitionLocked() || (wasEnabled != willBeEnabled) - || deviceHasNewLogicalDisplayId; + || deviceHasNewLogicalDisplayId + || displayNotInBothLayouts; if (isTransitioning) { - setDisplayPhase(logicalDisplay, phase); - if (phase == LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION) { - mUpdatedLogicalDisplays.put(displayId, UPDATE_STATE_TRANSITION); + if (isStateChangeStarting != logicalDisplay.isInTransitionLocked()) { + Slog.i(TAG, "Set isInTransition on display " + displayId + ": " + + isStateChangeStarting); } + // This will either mark the display as "transitioning" if we are starting to change + // the device state, or remove the transitioning marker if the state change is + // ending. + logicalDisplay.setIsInTransitionLocked(isStateChangeStarting); + mUpdatedLogicalDisplays.put(displayId, UPDATE_STATE_TRANSITION); } } } @@ -891,9 +922,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { newDisplay.swapDisplaysLocked(oldDisplay); } - if (!displayLayout.isEnabled()) { - setDisplayPhase(newDisplay, LogicalDisplay.DISPLAY_PHASE_DISABLED); - } + setEnabledLocked(newDisplay, displayLayout.isEnabled()); } } @@ -912,23 +941,25 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { final LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device); display.updateLocked(mDisplayDeviceRepo); mLogicalDisplays.put(displayId, display); - setDisplayPhase(display, LogicalDisplay.DISPLAY_PHASE_ENABLED); return display; } - private void setDisplayPhase(LogicalDisplay display, @DisplayPhase int phase) { + private void setEnabledLocked(LogicalDisplay display, boolean isEnabled) { final int displayId = display.getDisplayIdLocked(); final DisplayInfo info = display.getDisplayInfoLocked(); final boolean disallowSecondaryDisplay = mSingleDisplayDemoMode && (info.type != Display.TYPE_INTERNAL); - if (phase != LogicalDisplay.DISPLAY_PHASE_DISABLED && disallowSecondaryDisplay) { + if (isEnabled && disallowSecondaryDisplay) { Slog.i(TAG, "Not creating a logical display for a secondary display because single" + " display demo mode is enabled: " + display.getDisplayInfoLocked()); - phase = LogicalDisplay.DISPLAY_PHASE_DISABLED; + isEnabled = false; } - display.setPhase(phase); + if (display.isEnabledLocked() != isEnabled) { + Slog.i(TAG, "SetEnabled on display " + displayId + ": " + isEnabled); + display.setEnabledLocked(isEnabled); + } } private int assignDisplayGroupIdLocked(boolean isOwnDisplayGroup) { diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java index 1e97c1c5c5bcf..2edb909258f92 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -287,7 +287,7 @@ public class DisplayManagerServiceTest { when(mMockAppToken.asBinder()).thenReturn(mMockAppToken); - final int displayIds[] = bs.getDisplayIds(); + final int[] displayIds = bs.getDisplayIds(/* includeDisabled= */ true); final int size = displayIds.length; assertTrue(size > 0); @@ -1174,7 +1174,8 @@ public class DisplayManagerServiceTest { DisplayManagerService.BinderService displayManagerBinderService, FakeDisplayDevice displayDevice) { - final int[] displayIds = displayManagerBinderService.getDisplayIds(); + final int[] displayIds = displayManagerBinderService.getDisplayIds( + /* includeDisabled= */ true); assertTrue(displayIds.length > 0); int displayId = Display.INVALID_DISPLAY; for (int i = 0; i < displayIds.length; i++) { 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 cc68ba88f76ef..d515fae4afe28 100644 --- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java +++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java @@ -30,6 +30,8 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; @@ -53,6 +55,8 @@ import android.view.DisplayInfo; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.server.display.layout.Layout; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -60,6 +64,7 @@ import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.Spy; import java.io.InputStream; import java.io.OutputStream; @@ -85,6 +90,7 @@ public class LogicalDisplayMapperTest { @Mock Resources mResourcesMock; @Mock IPowerManager mIPowerManagerMock; @Mock IThermalService mIThermalServiceMock; + @Spy DeviceStateToLayoutMap mDeviceStateToLayoutMapSpy = new DeviceStateToLayoutMap(); @Captor ArgumentCaptor mDisplayCaptor; @@ -134,7 +140,8 @@ public class LogicalDisplayMapperTest { mLooper = new TestLooper(); mHandler = new Handler(mLooper.getLooper()); mLogicalDisplayMapper = new LogicalDisplayMapper(mContextMock, mDisplayDeviceRepo, - mListenerMock, new DisplayManagerService.SyncRoot(), mHandler); + mListenerMock, new DisplayManagerService.SyncRoot(), mHandler, + mDeviceStateToLayoutMapSpy); } @@ -261,7 +268,8 @@ public class LogicalDisplayMapperTest { add(createDisplayDevice(Display.TYPE_EXTERNAL, 600, 800, 0)); add(createDisplayDevice(Display.TYPE_VIRTUAL, 600, 800, 0)); - int [] ids = mLogicalDisplayMapper.getDisplayIdsLocked(Process.SYSTEM_UID); + int [] ids = mLogicalDisplayMapper.getDisplayIdsLocked(Process.SYSTEM_UID, + /* includeDisabled= */ true); assertEquals(3, ids.length); Arrays.sort(ids); assertEquals(DEFAULT_DISPLAY, ids[0]); @@ -413,6 +421,178 @@ public class LogicalDisplayMapperTest { /* isBootCompleted= */true)); } + @Test + public void testDeviceStateLocked() { + DisplayDevice device1 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, + DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY); + DisplayDevice device2 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, + DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY); + + Layout layout = new Layout(); + layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address, true, true); + layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address, false, false); + when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(layout); + + layout = new Layout(); + layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address, false, false); + layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address, true, true); + when(mDeviceStateToLayoutMapSpy.get(1)).thenReturn(layout); + when(mDeviceStateToLayoutMapSpy.get(2)).thenReturn(layout); + + LogicalDisplay display1 = add(device1); + assertEquals(info(display1).address, info(device1).address); + assertEquals(DEFAULT_DISPLAY, id(display1)); + + LogicalDisplay display2 = add(device2); + assertEquals(info(display2).address, info(device2).address); + // We can only have one default display + assertEquals(DEFAULT_DISPLAY, id(display1)); + + mLogicalDisplayMapper.setDeviceStateLocked(0, false); + mLooper.moveTimeForward(1000); + mLooper.dispatchAll(); + assertTrue(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked()); + assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked()); + assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isInTransitionLocked()); + assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isInTransitionLocked()); + + mLogicalDisplayMapper.setDeviceStateLocked(1, false); + mLooper.moveTimeForward(1000); + mLooper.dispatchAll(); + assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked()); + assertTrue(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked()); + assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isInTransitionLocked()); + assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isInTransitionLocked()); + + mLogicalDisplayMapper.setDeviceStateLocked(2, false); + mLooper.moveTimeForward(1000); + mLooper.dispatchAll(); + assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked()); + assertTrue(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked()); + assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isInTransitionLocked()); + assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isInTransitionLocked()); + } + + @Test + public void testEnabledAndDisabledDisplays() { + DisplayAddress displayAddressOne = new TestUtils.TestDisplayAddress(); + DisplayAddress displayAddressTwo = new TestUtils.TestDisplayAddress(); + DisplayAddress displayAddressThree = new TestUtils.TestDisplayAddress(); + + TestDisplayDevice device1 = createDisplayDevice(displayAddressOne, Display.TYPE_INTERNAL, + 600, 800, + DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY); + TestDisplayDevice device2 = createDisplayDevice(displayAddressTwo, Display.TYPE_INTERNAL, + 200, 800, + DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP); + TestDisplayDevice device3 = createDisplayDevice(displayAddressThree, Display.TYPE_INTERNAL, + 600, 900, DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP); + + Layout threeDevicesEnabledLayout = new Layout(); + threeDevicesEnabledLayout.createDisplayLocked( + displayAddressOne, + /* isDefault= */ true, + /* isEnabled= */ true); + threeDevicesEnabledLayout.createDisplayLocked( + displayAddressTwo, + /* isDefault= */ false, + /* isEnabled= */ true); + threeDevicesEnabledLayout.createDisplayLocked( + displayAddressThree, + /* isDefault= */ false, + /* isEnabled= */ true); + + when(mDeviceStateToLayoutMapSpy.get(DeviceStateToLayoutMap.STATE_DEFAULT)) + .thenReturn(threeDevicesEnabledLayout); + + LogicalDisplay display1 = add(device1); + LogicalDisplay display2 = add(device2); + LogicalDisplay display3 = add(device3); + + // ensure 3 displays are returned + int [] ids = mLogicalDisplayMapper.getDisplayIdsLocked(Process.SYSTEM_UID, false); + assertEquals(3, ids.length); + Arrays.sort(ids); + assertEquals(DEFAULT_DISPLAY, ids[0]); + assertNotNull(mLogicalDisplayMapper.getDisplayLocked(device1, + /* includeDisabled= */ false)); + assertNotNull(mLogicalDisplayMapper.getDisplayLocked(device2, + /* includeDisabled= */ false)); + assertNotNull(mLogicalDisplayMapper.getDisplayLocked(device3, + /* includeDisabled= */ false)); + assertNotNull(mLogicalDisplayMapper.getDisplayLocked( + threeDevicesEnabledLayout.getByAddress(displayAddressOne).getLogicalDisplayId(), + /* includeDisabled= */ false)); + assertNotNull(mLogicalDisplayMapper.getDisplayLocked( + threeDevicesEnabledLayout.getByAddress(displayAddressTwo).getLogicalDisplayId(), + /* includeDisabled= */ false)); + assertNotNull(mLogicalDisplayMapper.getDisplayLocked( + threeDevicesEnabledLayout.getByAddress(displayAddressThree).getLogicalDisplayId(), + /* includeDisabled= */ false)); + + Layout oneDeviceEnabledLayout = new Layout(); + oneDeviceEnabledLayout.createDisplayLocked( + displayAddressOne, + /* isDefault= */ true, + /* isEnabled= */ true); + oneDeviceEnabledLayout.createDisplayLocked( + displayAddressTwo, + /* isDefault= */ false, + /* isEnabled= */ false); + oneDeviceEnabledLayout.createDisplayLocked( + displayAddressThree, + /* isDefault= */ false, + /* isEnabled= */ false); + + when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(oneDeviceEnabledLayout); + when(mDeviceStateToLayoutMapSpy.get(1)).thenReturn(threeDevicesEnabledLayout); + + // 1) Set the new state + // 2) Mark the displays as STATE_OFF so that it can continue with transition + // 3) Send DISPLAY_DEVICE_EVENT_CHANGE to inform the mapper of the new display state + // 4) Dispatch handler events. + mLogicalDisplayMapper.setDeviceStateLocked(0, false); + mDisplayDeviceRepo.onDisplayDeviceEvent(device3, DISPLAY_DEVICE_EVENT_CHANGED); + mLooper.moveTimeForward(1000); + mLooper.dispatchAll(); + final int[] allDisplayIds = mLogicalDisplayMapper.getDisplayIdsLocked( + Process.SYSTEM_UID, false); + if (allDisplayIds.length != 1) { + throw new RuntimeException("Displays: \n" + + mLogicalDisplayMapper.getDisplayLocked(device1).toString() + + "\n" + mLogicalDisplayMapper.getDisplayLocked(device2).toString() + + "\n" + mLogicalDisplayMapper.getDisplayLocked(device3).toString()); + } + // ensure only one display is returned + assertEquals(1, allDisplayIds.length); + assertNotNull(mLogicalDisplayMapper.getDisplayLocked(device1, + /* includeDisabled= */ false)); + assertNull(mLogicalDisplayMapper.getDisplayLocked(device2, + /* includeDisabled= */ false)); + assertNull(mLogicalDisplayMapper.getDisplayLocked(device3, + /* includeDisabled= */ false)); + assertNotNull(mLogicalDisplayMapper.getDisplayLocked( + oneDeviceEnabledLayout.getByAddress(displayAddressOne).getLogicalDisplayId(), + /* includeDisabled= */ false)); + assertNull(mLogicalDisplayMapper.getDisplayLocked( + oneDeviceEnabledLayout.getByAddress(displayAddressTwo).getLogicalDisplayId(), + /* includeDisabled= */ false)); + assertNull(mLogicalDisplayMapper.getDisplayLocked( + oneDeviceEnabledLayout.getByAddress(displayAddressThree).getLogicalDisplayId(), + /* includeDisabled= */ false)); + + // Now do it again to go back to state 1 + mLogicalDisplayMapper.setDeviceStateLocked(1, false); + mDisplayDeviceRepo.onDisplayDeviceEvent(device3, DISPLAY_DEVICE_EVENT_CHANGED); + mLooper.moveTimeForward(1000); + mLooper.dispatchAll(); + final int[] threeDisplaysEnabled = mLogicalDisplayMapper.getDisplayIdsLocked( + Process.SYSTEM_UID, false); + + // ensure all three displays are returned + assertEquals(3, threeDisplaysEnabled.length); + } + ///////////////// // Helper Methods ///////////////// @@ -477,6 +657,7 @@ public class LogicalDisplayMapperTest { class TestDisplayDevice extends DisplayDevice { private DisplayDeviceInfo mInfo; private DisplayDeviceInfo mSentInfo; + private int mState; TestDisplayDevice() { super(null, null, "test_display_" + sUniqueTestDisplayId++, mContextMock); diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java index b0738fdb78d0c..50d2a51b4bbe7 100644 --- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java +++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java @@ -128,12 +128,12 @@ public class LogicalDisplayTest { verify(t).setDisplayFlags(any(), eq(SurfaceControl.DISPLAY_RECEIVES_INPUT)); reset(t); - mLogicalDisplay.setPhase(LogicalDisplay.DISPLAY_PHASE_DISABLED); + mLogicalDisplay.setEnabledLocked(false); mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false); verify(t).setDisplayFlags(any(), eq(0)); reset(t); - mLogicalDisplay.setPhase(LogicalDisplay.DISPLAY_PHASE_ENABLED); + mLogicalDisplay.setEnabledLocked(true); mDisplayDeviceInfo.touch = DisplayDeviceInfo.TOUCH_EXTERNAL; mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false); verify(t).setDisplayFlags(any(), eq(SurfaceControl.DISPLAY_RECEIVES_INPUT));