From 42460b0bd604670e3aee2a39d869e0aad204b666 Mon Sep 17 00:00:00 2001 From: Santos Cordon Date: Tue, 15 Nov 2022 14:32:08 +0000 Subject: [PATCH] Do not send updates for disabled displays. If a display is disabled, do not share it outside of DisplayManagerService. This will help by not creating any unnecessary updates for displays that are disabled, and also not informing Applications of displays that they are unable to use. Also fixing a concurrency issue with DMS.handleLogicalDisplaySwapped which became apparent with this CL. Deferring the runnable with a Handler.post() caused the display-state-OFF update to happen after a newer change to turn the display ON. This caused the device to startup into a black screen. Removed the unnecessary call (since the displays are now made to both go into the "transition" state) to update the state and removed a lot of the redundant code with handleLogicalDisplayChange() which was called at the end of the method anyway. Bug: 221071695 Test: atest com.server.android.display Test: Manually verify display layouts still successfully change, and also confirm logs no longer hand excessive add/remove display notifications. Change-Id: I003fd91567630a447cf030b6fb83e5cb1041bcb5 --- .../hardware/display/DisplayManager.java | 12 +- .../display/DisplayManagerGlobal.java | 12 +- .../hardware/display/IDisplayManager.aidl | 2 +- .../server/display/DisplayManagerService.java | 44 ++--- .../server/display/DisplayModeDirector.java | 9 +- .../display/DisplayPowerController.java | 36 ++-- .../display/DisplayPowerController2.java | 35 ++-- .../DisplayPowerControllerInterface.java | 5 - .../server/display/DisplayPowerState.java | 11 +- .../server/display/LogicalDisplay.java | 95 ++++----- .../server/display/LogicalDisplayMapper.java | 101 ++++++---- .../display/DisplayPowerController2Test.java | 4 +- .../display/DisplayPowerControllerTest.java | 6 +- .../display/DisplayManagerServiceTest.java | 5 +- .../display/LogicalDisplayMapperTest.java | 186 +++++++++++++++++- .../server/display/LogicalDisplayTest.java | 4 +- 16 files changed, 404 insertions(+), 163 deletions(-) diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index 441fd88f15ee0..f7675e835eb20 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -588,18 +588,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 cc397d57d8388..f038c66d3b51a 100644 --- a/core/java/android/hardware/display/DisplayManagerGlobal.java +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -212,6 +212,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) { @@ -220,7 +230,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 6b5594b1a3ddf..28bb35f7d9cf4 100644 --- a/core/java/android/hardware/display/IDisplayManager.aidl +++ b/core/java/android/hardware/display/IDisplayManager.aidl @@ -37,7 +37,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 c5cb08db934f6..1d04f2ef99c0c 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -1573,7 +1573,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) { @@ -1592,7 +1592,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(); @@ -1622,7 +1622,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)) { @@ -1638,23 +1638,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); } - DisplayPowerControllerInterface dpc = mDisplayPowerControllers.get(displayId); - if (dpc != null) { - dpc.onDisplayChanged(); - } - mPersistentDataStore.saveIfNeeded(); mHandler.sendEmptyMessage(MSG_LOAD_BRIGHTNESS_CONFIGURATIONS); - handleLogicalDisplayChangedLocked(display); } private void notifyDefaultDisplayDeviceUpdated(LogicalDisplay display) { @@ -1666,7 +1656,7 @@ public final class DisplayManagerService extends SystemService { final int displayId = display.getDisplayIdLocked(); final DisplayPowerControllerInterface dpc = mDisplayPowerControllers.get(displayId); if (dpc != null) { - dpc.onDeviceStateTransition(); + dpc.onDisplayChanged(); } } @@ -2366,9 +2356,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) { @@ -2653,8 +2647,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) { @@ -2871,12 +2864,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); @@ -3367,6 +3360,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; + } DisplayPowerControllerInterface 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 306b8cf4e0eec..40e7c5062a774 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; @@ -1640,7 +1641,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); @@ -2517,7 +2518,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)); } } @@ -2528,7 +2530,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 d6f0fd070f941..81245001eaf6d 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -497,6 +497,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. */ @@ -520,6 +523,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); @@ -807,28 +812,35 @@ 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) { updatePowerState(); } }); } - /** - * 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. - */ - @Override - public void onDeviceStateTransition() { - sendUpdatePowerState(); - } - /** * Unregisters all listeners and interrupts all running threads; halting future work. * @@ -1316,8 +1328,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/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java index 1f58a1c40fd81..9a594e8e059ed 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController2.java +++ b/services/core/java/com/android/server/display/DisplayPowerController2.java @@ -422,6 +422,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal private DisplayDeviceConfig mDisplayDeviceConfig; + private boolean mIsEnabled; + private boolean mIsInTransition; /** * Creates the display power controller. */ @@ -439,6 +441,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal mHandler = new DisplayControllerHandler(handler.getLooper()); mDisplayDeviceConfig = logicalDisplay.getPrimaryDisplayDeviceLocked() .getDisplayDeviceConfig(); + mIsEnabled = logicalDisplay.isEnabledLocked(); + mIsInTransition = logicalDisplay.isInTransitionLocked(); mWakelockController = mInjector.getWakelockController(mDisplayId, callbacks); mDisplayPowerProximityStateController = mInjector.getDisplayPowerProximityStateController( mWakelockController, mDisplayDeviceConfig, mHandler.getLooper(), @@ -721,29 +725,36 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal 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); mDisplayPowerProximityStateController.notifyDisplayDeviceChanged(config); + + // 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) { updatePowerState(); } }); } - /** - * 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. - */ - @Override - public void onDeviceStateTransition() { - sendUpdatePowerState(); - } - /** * Unregisters all listeners and interrupts all running threads; halting future work. * @@ -1165,8 +1176,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal mDisplayPowerProximityStateController.updateProximityState(mPowerRequest, state); - if (!mLogicalDisplay.isEnabled() - || mLogicalDisplay.getPhase() == LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION + if (!mIsEnabled + || mIsInTransition || mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity()) { state = Display.STATE_OFF; } diff --git a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java index 6677f358557d8..46f1343ceeb80 100644 --- a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java +++ b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java @@ -44,11 +44,6 @@ public interface DisplayPowerControllerInterface { */ void stop(); - /** - * Used to manage the displays preparing to transition from one device state to another. - */ - void onDeviceStateTransition(); - /** * Used to update the display's BrightnessConfiguration * @param config The new BrightnessConfiguration 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 28bdce3d8b34a..8dd169bf4bf6c 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; } /** @@ -531,7 +505,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); @@ -773,32 +747,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 cb97e28328547..66073c2abdec8 100644 --- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java +++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java @@ -40,7 +40,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; @@ -180,6 +179,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(); @@ -194,7 +199,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 @@ -231,10 +236,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; } @@ -242,21 +259,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) { @@ -390,14 +412,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, /* transitionValue= */ true); mPendingDeviceState = state; final boolean wakeDevice = shouldDeviceBeWoken(mPendingDeviceState, mDeviceState, mInteractive, mBootCompleted); @@ -507,7 +527,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; } @@ -523,7 +543,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { } private void transitionToPendingStateLocked() { - resetLayoutLocked(mDeviceState, mPendingDeviceState, LogicalDisplay.DISPLAY_PHASE_ENABLED); + resetLayoutLocked(mDeviceState, mPendingDeviceState, /* transitionValue= */ false); mDeviceState = mPendingDeviceState; mPendingDeviceState = DeviceStateManager.INVALID_DEVICE_STATE; applyLayoutLocked(); @@ -838,17 +858,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 transitionValue The value to mark the transition state: true == transitioning. */ - private void resetLayoutLocked(int fromState, int toState, @DisplayPhase int phase) { + private void resetLayoutLocked(int fromState, int toState, boolean transitionValue) { final Layout fromLayout = mDeviceStateToLayoutMap.get(fromState); final Layout toLayout = mDeviceStateToLayoutMap.get(toState); @@ -866,12 +886,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(); @@ -886,16 +910,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 (transitionValue != logicalDisplay.isInTransitionLocked()) { + Slog.i(TAG, "Set isInTransition on display " + displayId + ": " + + transitionValue); } + // 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(transitionValue); + mUpdatedLogicalDisplays.put(displayId, UPDATE_STATE_TRANSITION); } } } @@ -940,9 +971,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { newDisplay.swapDisplaysLocked(oldDisplay); } - if (!displayLayout.isEnabled()) { - setDisplayPhase(newDisplay, LogicalDisplay.DISPLAY_PHASE_DISABLED); - } + setEnabledLocked(newDisplay, displayLayout.isEnabled()); } } @@ -961,23 +990,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( diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java index 4c28c51f7e620..f2cba40685e41 100644 --- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java +++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java @@ -237,8 +237,8 @@ public final class DisplayPowerController2Test { when(mLogicalDisplayMock.getDisplayIdLocked()).thenReturn(displayId); when(mLogicalDisplayMock.getPrimaryDisplayDeviceLocked()).thenReturn(mDisplayDeviceMock); when(mLogicalDisplayMock.getDisplayInfoLocked()).thenReturn(info); - when(mLogicalDisplayMock.isEnabled()).thenReturn(true); - when(mLogicalDisplayMock.getPhase()).thenReturn(LogicalDisplay.DISPLAY_PHASE_ENABLED); + when(mLogicalDisplayMock.isEnabledLocked()).thenReturn(true); + when(mLogicalDisplayMock.isInTransitionLocked()).thenReturn(false); when(mDisplayDeviceMock.getDisplayDeviceInfoLocked()).thenReturn(deviceInfo); when(mDisplayDeviceMock.getUniqueId()).thenReturn(uniqueId); when(mDisplayDeviceMock.getDisplayDeviceConfig()).thenReturn(mDisplayDeviceConfigMock); diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java index 9a4bb22d51953..4f8cb8876b3f5 100644 --- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java @@ -219,8 +219,8 @@ public final class DisplayPowerControllerTest { when(mLogicalDisplayMock.getDisplayIdLocked()).thenReturn(displayId); when(mLogicalDisplayMock.getPrimaryDisplayDeviceLocked()).thenReturn(mDisplayDeviceMock); when(mLogicalDisplayMock.getDisplayInfoLocked()).thenReturn(info); - when(mLogicalDisplayMock.isEnabled()).thenReturn(true); - when(mLogicalDisplayMock.getPhase()).thenReturn(LogicalDisplay.DISPLAY_PHASE_ENABLED); + when(mLogicalDisplayMock.isEnabledLocked()).thenReturn(true); + when(mLogicalDisplayMock.isInTransitionLocked()).thenReturn(false); when(mDisplayDeviceMock.getDisplayDeviceInfoLocked()).thenReturn(deviceInfo); when(mDisplayDeviceMock.getUniqueId()).thenReturn(uniqueId); when(mDisplayDeviceMock.getDisplayDeviceConfig()).thenReturn(mDisplayDeviceConfigMock); @@ -233,4 +233,4 @@ public final class DisplayPowerControllerTest { }); when(mDisplayDeviceConfigMock.getNits()).thenReturn(new float[]{2, 500}); } -} \ No newline at end of file +} 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 ce35626a51063..109abd03747c0 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -319,7 +319,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); @@ -1417,7 +1417,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 657bda633ab5c..246945c2d968c 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; @@ -52,6 +54,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; @@ -59,6 +63,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; @@ -83,6 +88,7 @@ public class LogicalDisplayMapperTest { @Mock Resources mResourcesMock; @Mock IPowerManager mIPowerManagerMock; @Mock IThermalService mIThermalServiceMock; + @Spy DeviceStateToLayoutMap mDeviceStateToLayoutMapSpy = new DeviceStateToLayoutMap(); @Captor ArgumentCaptor mDisplayCaptor; @@ -132,7 +138,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); } @@ -259,7 +266,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]); @@ -503,10 +511,183 @@ 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); + advanceTime(1000); + assertTrue(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked()); + assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked()); + assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isInTransitionLocked()); + assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isInTransitionLocked()); + + mLogicalDisplayMapper.setDeviceStateLocked(1, false); + advanceTime(1000); + assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked()); + assertTrue(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked()); + assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isInTransitionLocked()); + assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isInTransitionLocked()); + + mLogicalDisplayMapper.setDeviceStateLocked(2, false); + advanceTime(1000); + 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, "one", + Display.TYPE_INTERNAL, 600, 800, + DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY); + TestDisplayDevice device2 = createDisplayDevice(displayAddressTwo, "two", + Display.TYPE_INTERNAL, 200, 800, + DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP); + TestDisplayDevice device3 = createDisplayDevice(displayAddressThree, "three", + 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); + advanceTime(1000); + 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); + advanceTime(1000); + final int[] threeDisplaysEnabled = mLogicalDisplayMapper.getDisplayIdsLocked( + Process.SYSTEM_UID, false); + + // ensure all three displays are returned + assertEquals(3, threeDisplaysEnabled.length); + } + ///////////////// // Helper Methods ///////////////// + private void advanceTime(long timeMs) { + mLooper.moveTimeForward(1000); + mLooper.dispatchAll(); + } + private TestDisplayDevice createDisplayDevice(int type, int width, int height, int flags) { return createDisplayDevice( new TestUtils.TestDisplayAddress(), /* uniqueId */ "", type, width, height, flags); @@ -575,6 +756,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 5a43530d44ddb..1d70fc61c937a 100644 --- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java +++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java @@ -126,12 +126,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));