From fc8087ce7b50877d6a73ba85976212258b87718e Mon Sep 17 00:00:00 2001 From: Louis Chang Date: Fri, 21 Apr 2023 06:31:44 +0000 Subject: [PATCH] Prevents activity being stopped while folding/unfolding device Display is turning off while swapping the physical display, usually happens while folding/unfolding a foldable device. The display will be turn back on after some time later, but the top activity could be stopped if the display is not turning back on soon enough. Bug: 271098735 Test: tested on Youtube and some other apps Change-Id: I687f35f4989f9e23276451c9b3a3ce8578e64280 Merged-In: I687f35f4989f9e23276451c9b3a3ce8578e64280 --- .../display/DisplayPowerController.java | 4 ++-- .../server/policy/PhoneWindowManager.java | 10 ++++---- .../server/policy/WindowManagerPolicy.java | 4 +++- .../com/android/server/wm/ActivityRecord.java | 10 ++++++++ .../wm/ActivityTaskManagerInternal.java | 7 ++++++ .../server/wm/ActivityTaskManagerService.java | 8 ++++++- .../server/wm/ActivityTaskSupervisor.java | 17 ++++++++++++- .../server/wm/RootWindowContainer.java | 24 +++++++++++++++++-- .../server/wm/TestWindowManagerPolicy.java | 2 +- 9 files changed, 73 insertions(+), 13 deletions(-) diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 47e880a3fce20..8eed76505847a 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -2094,14 +2094,14 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call && !mScreenOffBecauseOfProximity) { setReportedScreenState(REPORTED_TO_POLICY_SCREEN_OFF); unblockScreenOn(); - mWindowManagerPolicy.screenTurnedOff(mDisplayId); + mWindowManagerPolicy.screenTurnedOff(mDisplayId, mIsInTransition); } else if (!isOff && mReportedScreenStateToPolicy == REPORTED_TO_POLICY_SCREEN_TURNING_OFF) { // We told policy already that screen was turning off, but now we changed our minds. // Complete the full state transition on -> turningOff -> off. unblockScreenOff(); - mWindowManagerPolicy.screenTurnedOff(mDisplayId); + mWindowManagerPolicy.screenTurnedOff(mDisplayId, mIsInTransition); setReportedScreenState(REPORTED_TO_POLICY_SCREEN_OFF); } if (!isOff diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index be4fe09d593c2..47cb51e554aaa 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -4752,11 +4752,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { // Called on the DisplayManager's DisplayPowerController thread. @Override - public void screenTurnedOff(int displayId) { + public void screenTurnedOff(int displayId, boolean isSwappingDisplay) { if (DEBUG_WAKEUP) Slog.i(TAG, "Display" + displayId + " turned off..."); if (displayId == DEFAULT_DISPLAY) { - updateScreenOffSleepToken(true); + updateScreenOffSleepToken(true, isSwappingDisplay); mRequestedOrSleepingDefaultDisplay = false; mDefaultDisplayPolicy.screenTurnedOff(); synchronized (mLock) { @@ -4784,7 +4784,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (displayId == DEFAULT_DISPLAY) { Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "screenTurningOn", 0 /* cookie */); - updateScreenOffSleepToken(false); + updateScreenOffSleepToken(false /* acquire */, false /* isSwappingDisplay */); mDefaultDisplayPolicy.screenTurnedOn(screenOnListener); mBootAnimationDismissable = false; @@ -5301,9 +5301,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { } // TODO (multidisplay): Support multiple displays in WindowManagerPolicy. - private void updateScreenOffSleepToken(boolean acquire) { + private void updateScreenOffSleepToken(boolean acquire, boolean isSwappingDisplay) { if (acquire) { - mScreenOffSleepTokenAcquirer.acquire(DEFAULT_DISPLAY); + mScreenOffSleepTokenAcquirer.acquire(DEFAULT_DISPLAY, isSwappingDisplay); } else { mScreenOffSleepTokenAcquirer.release(DEFAULT_DISPLAY); } diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java index 77007fa229a27..cd63caa063f09 100644 --- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java +++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java @@ -829,8 +829,10 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { /** * Called when the display has turned off. + * @param displayId The display to apply to. + * @param isSwappingDisplay Whether the display is swapping to another physical display. */ - public void screenTurnedOff(int displayId); + void screenTurnedOff(int displayId, boolean isSwappingDisplay); public interface ScreenOnListener { void onScreenOn(); diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 2f7673a309a2e..4da08c46d04ba 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -8415,6 +8415,16 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return inTransitionSelfOrParent(); } + boolean isDisplaySleepingAndSwapping() { + for (int i = mDisplayContent.mAllSleepTokens.size() - 1; i >= 0; i--) { + RootWindowContainer.SleepToken sleepToken = mDisplayContent.mAllSleepTokens.get(i); + if (sleepToken.isDisplaySwapping()) { + return true; + } + } + return false; + } + /** * Whether this activity is letterboxed for fixed orientation. If letterboxed due to fixed * orientation then aspect ratio restrictions are also already respected. diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java index 2792f4265caa5..9123ffa329dff 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java @@ -140,6 +140,13 @@ public abstract class ActivityTaskManagerInternal { */ void acquire(int displayId); + /** + * Acquires a sleep token. + * @param displayId The display to apply to. + * @param isSwappingDisplay Whether the display is swapping to another physical display. + */ + void acquire(int displayId, boolean isSwappingDisplay); + /** * Releases the sleep token. * @param displayId The display to apply to. diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index f6e92a6429003..863bf94b13c1b 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -4747,10 +4747,16 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { @Override public void acquire(int displayId) { + acquire(displayId, false /* isSwappingDisplay */); + } + + @Override + public void acquire(int displayId, boolean isSwappingDisplay) { synchronized (mGlobalLock) { if (!mSleepTokens.contains(displayId)) { mSleepTokens.append(displayId, - mRootWindowContainer.createSleepToken(mTag, displayId)); + mRootWindowContainer.createSleepToken(mTag, displayId, + isSwappingDisplay)); updateSleepIfNeededLocked(); } } diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index 9b9337e24f53d..371480884391a 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -179,6 +179,9 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { // How long we can hold the launch wake lock before giving up. private static final int LAUNCH_TIMEOUT = 10 * 1000 * Build.HW_TIMEOUT_MULTIPLIER; + // How long we delay processing the stopping and finishing activities. + private static final int SCHEDULE_FINISHING_STOPPING_ACTIVITY_MS = 200; + /** How long we wait until giving up on the activity telling us it released the top state. */ private static final int TOP_RESUMED_STATE_LOSS_TIMEOUT = 500; @@ -1933,13 +1936,15 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { boolean processPausingActivities, String reason) { // Stop any activities that are scheduled to do so but have been waiting for the transition // animation to finish. + boolean displaySwapping = false; ArrayList readyToStopActivities = null; for (int i = mStoppingActivities.size() - 1; i >= 0; --i) { final ActivityRecord s = mStoppingActivities.get(i); final boolean animating = s.isInTransition(); + displaySwapping |= s.isDisplaySleepingAndSwapping(); ProtoLog.v(WM_DEBUG_STATES, "Stopping %s: nowVisible=%b animating=%b " + "finishing=%s", s, s.nowVisible, animating, s.finishing); - if (!animating || mService.mShuttingDown) { + if ((!animating && !displaySwapping) || mService.mShuttingDown) { if (!processPausingActivities && s.isState(PAUSING)) { // Defer processing pausing activities in this iteration and reschedule // a delayed idle to reprocess it again @@ -1958,6 +1963,16 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { } } + // Stopping activities are deferred processing if the display is swapping. Check again + // later to ensure the stopping activities can be stopped after display swapped. + if (displaySwapping) { + mHandler.postDelayed(() -> { + synchronized (mService.mGlobalLock) { + scheduleProcessStoppingAndFinishingActivitiesIfNeeded(); + } + }, SCHEDULE_FINISHING_STOPPING_ACTIVITY_MS); + } + final int numReadyStops = readyToStopActivities == null ? 0 : readyToStopActivities.size(); for (int i = 0; i < numReadyStops; i++) { final ActivityRecord r = readyToStopActivities.get(i); diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 2507a8d5af3a4..6114f27a2508c 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -2570,6 +2570,10 @@ class RootWindowContainer extends WindowContainer } SleepToken createSleepToken(String tag, int displayId) { + return createSleepToken(tag, displayId, false /* isSwappingDisplay */); + } + + SleepToken createSleepToken(String tag, int displayId, boolean isSwappingDisplay) { final DisplayContent display = getDisplayContent(displayId); if (display == null) { throw new IllegalArgumentException("Invalid display: " + displayId); @@ -2578,7 +2582,7 @@ class RootWindowContainer extends WindowContainer final int tokenKey = makeSleepTokenKey(tag, displayId); SleepToken token = mSleepTokens.get(tokenKey); if (token == null) { - token = new SleepToken(tag, displayId); + token = new SleepToken(tag, displayId, isSwappingDisplay); mSleepTokens.put(tokenKey, token); display.mAllSleepTokens.add(token); ProtoLog.d(WM_DEBUG_STATES, "Create sleep token: tag=%s, displayId=%d", tag, displayId); @@ -3527,18 +3531,34 @@ class RootWindowContainer extends WindowContainer private final String mTag; private final long mAcquireTime; private final int mDisplayId; + private final boolean mIsSwappingDisplay; final int mHashKey; - SleepToken(String tag, int displayId) { + // The display could remain in sleep after the physical display swapped, adding a 1 + // seconds display swap timeout to prevent activities staying in PAUSED state. + // Otherwise, the sleep token should be removed once display turns back on after swapped. + private static final long DISPLAY_SWAP_TIMEOUT = 1000; + + SleepToken(String tag, int displayId, boolean isSwappingDisplay) { mTag = tag; mDisplayId = displayId; mAcquireTime = SystemClock.uptimeMillis(); + mIsSwappingDisplay = isSwappingDisplay; mHashKey = makeSleepTokenKey(mTag, mDisplayId); } + public boolean isDisplaySwapping() { + long now = SystemClock.uptimeMillis(); + if (now - mAcquireTime > DISPLAY_SWAP_TIMEOUT) { + return false; + } + return mIsSwappingDisplay; + } + @Override public String toString() { return "{\"" + mTag + "\", display " + mDisplayId + + (mIsSwappingDisplay ? " is swapping " : "") + ", acquire at " + TimeUtils.formatUptime(mAcquireTime) + "}"; } diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java index e2db2e6b19e1b..148dd8dfd25a2 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java +++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java @@ -147,7 +147,7 @@ class TestWindowManagerPolicy implements WindowManagerPolicy { } @Override - public void screenTurnedOff(int displayId) { + public void screenTurnedOff(int displayId, boolean isSwappingDisplay) { } @Override