From 466cda4ec3d6ffbed41e639e24d6666fb1cd2016 Mon Sep 17 00:00:00 2001 From: Evan Rosky Date: Fri, 11 Jun 2021 20:00:05 -0700 Subject: [PATCH] Add multi-display support to shell transitions This tracks ready state in groups. Currently the groups are display-contents (to match legacy transitions). The transition will be considered ready when all groups are ready. This also adds a couple of inTransition checks in places that are used by CTS tests. Bug: 183993924 Test: atest MultiDisplaySystemDecorationTests Change-Id: Iea81b3c689d948d60aa8e1a2d3bf168723058a2f --- data/etc/services.core.protolog.json | 24 +++ .../android/server/wm/ActivityStarter.java | 2 +- .../server/wm/ActivityTaskSupervisor.java | 3 +- .../com/android/server/wm/DisplayContent.java | 11 +- .../server/wm/RootWindowContainer.java | 5 + .../com/android/server/wm/Transition.java | 139 ++++++++++++++++-- .../server/wm/TransitionController.java | 23 +-- .../android/server/wm/WindowContainer.java | 3 +- .../server/wm/WindowManagerService.java | 6 +- .../server/wm/WindowOrganizerController.java | 2 +- 10 files changed, 187 insertions(+), 31 deletions(-) diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 50e6b90679b29..3e37237628004 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -181,6 +181,12 @@ "group": "WM_DEBUG_STARTING_WINDOW", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, + "-1924376693": { + "message": " Setting Ready-group to %b. group=%s from %s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "-1918702467": { "message": "onSyncFinishedDrawing %s", "level": "VERBOSE", @@ -607,6 +613,12 @@ "group": "WM_ERROR", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, + "-1442613680": { + "message": " Creating Ready-group for Transition %d with root=%s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "-1438175584": { "message": "Input focus has changed to %s display=%d", "level": "VERBOSE", @@ -2209,6 +2221,12 @@ "group": "WM_DEBUG_ADD_REMOVE", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, + "352982444": { + "message": " allReady query: used=%b override=%b states=[%s]", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "355720268": { "message": "stopFreezingDisplayLocked: Unfreezing now", "level": "DEBUG", @@ -3277,6 +3295,12 @@ "group": "WM_DEBUG_IME", "at": "com\/android\/server\/wm\/InsetsStateController.java" }, + "1670933628": { + "message": " Setting allReady override", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "1671994402": { "message": "Nulling last startingData", "level": "VERBOSE", diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 10c1f6b252096..3168c8bfa1de5 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -1603,7 +1603,7 @@ class ActivityStarter { mTargetTask, remoteTransition); } else if (started) { // Make the collecting transition wait until this request is ready. - mService.getTransitionController().setReady(false); + mService.getTransitionController().setReady(r, false); } } } diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index 74efe0c7422f4..f6cca84fd5f4e 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -1359,7 +1359,8 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { } mService.getTransitionController().requestTransitionIfNeeded(TRANSIT_TO_FRONT, - 0 /* flags */, task, options != null ? options.getRemoteTransition() : null); + 0 /* flags */, task, task /* readyGroupRef */, + options != null ? options.getRemoteTransition() : null); reason = reason + " findTaskToMoveToFront"; boolean reparented = false; if (task.isResizeable() && canUseActivityOptionsLaunchBounds(options)) { diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 632662d5e7cc1..c7b3d23641b88 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -2987,7 +2987,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp @Override void removeIfPossible() { - if (isAnimating(TRANSITION | PARENTS)) { + if (isAnimating(TRANSITION | PARENTS) + // isAnimating is a legacy transition query and will be removed, so also add a + // check for whether this is in a shell-transition when not using legacy. + || mAtmService.getTransitionController().inTransition()) { mDeferredRemoval = true; return; } @@ -4989,7 +4992,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp @WindowManager.TransitionFlags int flags) { prepareAppTransition(transit, flags); mAtmService.getTransitionController().requestTransitionIfNeeded(transit, flags, - null /* trigger */); + null /* trigger */, this); } /** @see #requestTransitionAndLegacyPrepare(int, int) */ @@ -4997,11 +5000,11 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp @Nullable WindowContainer trigger) { prepareAppTransition(transit); mAtmService.getTransitionController().requestTransitionIfNeeded(transit, 0 /* flags */, - trigger); + trigger, this); } void executeAppTransition() { - mAtmService.getTransitionController().setReady(); + mAtmService.getTransitionController().setReady(this); if (mAppTransition.isTransitionSet()) { ProtoLog.w(WM_DEBUG_APP_TRANSITIONS, "Execute app transition: %s, displayId: %d Callers=%s", diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 0b80bae71ab83..75658a1592c9e 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -508,6 +508,11 @@ class RootWindowContainer extends WindowContainer mTaskSupervisor.updateTopResumedActivityIfNeeded(); } + @Override + boolean isAttached() { + return true; + } + /** * Called when DisplayWindowSettings values may change. */ diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 0a8a9373e141c..e2408d2c8aefa 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -132,7 +132,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe private TransitionInfo.AnimationOptions mOverrideOptions; private @TransitionState int mState = STATE_COLLECTING; - private boolean mReadyCalled = false; + private final ReadyTracker mReadyTracker = new ReadyTracker(); // TODO(b/188595497): remove when not needed. /** @see RecentsAnimationController#mNavigationBarAttachedToApp */ @@ -169,9 +169,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe mState = STATE_STARTED; ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Starting Transition %d", mSyncId); - if (mReadyCalled) { - setReady(); - } + applyReady(); } /** @@ -186,6 +184,11 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe for (WindowContainer curr = wc.getParent(); curr != null && !mChanges.containsKey(curr); curr = curr.getParent()) { mChanges.put(curr, new ChangeInfo(curr)); + if (isReadyGroup(curr)) { + mReadyTracker.addGroup(curr); + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Creating Ready-group for" + + " Transition %d with root=%s", mSyncId, curr); + } } if (mParticipants.contains(wc)) return; mSyncEngine.addToSyncSet(mSyncId, wc); @@ -236,21 +239,33 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe * all participants have finished drawing, the transition can still collect participants. * * If this is called before the transition is started, it will be deferred until start. + * + * @param wc A reference point to determine which ready-group to update. For now, each display + * has its own ready-group, so this is used to look-up which display to mark ready. + * The transition will wait for all groups to be ready. */ - void setReady(boolean ready) { + void setReady(WindowContainer wc, boolean ready) { if (mSyncId < 0) return; - if (mState < STATE_STARTED) { - mReadyCalled = ready; - return; - } + mReadyTracker.setReadyFrom(wc, ready); + applyReady(); + } + + private void applyReady() { + if (mState < STATE_STARTED) return; + final boolean ready = mReadyTracker.allReady(); ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Set transition ready=%b %d", ready, mSyncId); mSyncEngine.setReady(mSyncId, ready); } - /** @see #setReady . This calls with parameter true. */ - void setReady() { - setReady(true); + /** + * Sets all possible ready groups to ready. + * @see ReadyTracker#setAllReady. + */ + void setAllReady() { + if (mSyncId < 0) return; + mReadyTracker.setAllReady(); + applyReady(); } /** @@ -880,6 +895,15 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe return wc.getParent().getSurfaceControl(); } + /** + * A ready group is defined by a root window-container where all transitioning windows under + * it are expected to animate together as a group. At the moment, this treats each display as + * a ready-group to match the existing legacy transition behavior. + */ + private static boolean isReadyGroup(WindowContainer wc) { + return wc instanceof DisplayContent; + } + /** * Construct a TransitionInfo object from a set of targets and changes. Also populates the * root surface. @@ -1082,4 +1106,95 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe mChildren.addAll(wcs); } } + + /** + * The transition sync mechanism has 2 parts: + * 1. Whether all WM operations for a particular transition are "ready" (eg. did the app + * launch or stop or get a new configuration?). + * 2. Whether all the windows involved have finished drawing their final-state content. + * + * A transition animation can play once both parts are complete. This ready-tracker keeps track + * of part (1). Currently, WM code assumes that "readiness" (part 1) is grouped. This means that + * even if the WM operations in one group are ready, the whole transition itself may not be + * ready if there are WM operations still pending in another group. This class helps keep track + * of readiness across the multiple groups. Currently, we assume that each display is a group + * since that is how it has been until now. + */ + private static class ReadyTracker { + private final ArrayMap mReadyGroups = new ArrayMap<>(); + + /** + * Ensures that this doesn't report as allReady before it has been used. This is needed + * in very niche cases where a transition is a no-op (nothing has been collected) but we + * still want to be marked ready (via. setAllReady). + */ + private boolean mUsed = false; + + /** + * If true, this overrides all ready groups and reports ready. Used by shell-initiated + * transitions via {@link #setAllReady()}. + */ + private boolean mReadyOverride = false; + + /** + * Adds a ready-group. Any setReady calls in this subtree will be tracked together. For + * now these are only DisplayContents. + */ + void addGroup(WindowContainer wc) { + if (mReadyGroups.containsKey(wc)) { + Slog.e(TAG, "Trying to add a ready-group twice: " + wc); + return; + } + mReadyGroups.put(wc, false); + } + + /** + * Sets a group's ready state. + * @param wc Any container within a group's subtree. Used to identify the ready-group. + */ + void setReadyFrom(WindowContainer wc, boolean ready) { + mUsed = true; + WindowContainer current = wc; + while (current != null) { + if (isReadyGroup(current)) { + mReadyGroups.put(current, ready); + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Setting Ready-group to" + + " %b. group=%s from %s", ready, current, wc); + break; + } + current = current.getParent(); + } + } + + /** Marks this as ready regardless of individual groups. */ + void setAllReady() { + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Setting allReady override"); + mUsed = true; + mReadyOverride = true; + } + + /** @return true if all tracked subtrees are ready. */ + boolean allReady() { + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " allReady query: used=%b " + + "override=%b states=[%s]", mUsed, mReadyOverride, groupsToString()); + if (!mUsed) return false; + if (mReadyOverride) return true; + for (int i = mReadyGroups.size() - 1; i >= 0; --i) { + final WindowContainer wc = mReadyGroups.keyAt(i); + if (!wc.isAttached() || !wc.isVisibleRequested()) continue; + if (!mReadyGroups.valueAt(i)) return false; + } + return true; + } + + private String groupsToString() { + StringBuilder b = new StringBuilder(); + for (int i = 0; i < mReadyGroups.size(); ++i) { + if (i != 0) b.append(','); + b.append(mReadyGroups.keyAt(i)).append(':') + .append(mReadyGroups.valueAt(i)); + } + return b.toString(); + } + } } diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index 3d7b34b0d9137..16d22787500c0 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -192,8 +192,8 @@ class TransitionController { */ @Nullable Transition requestTransitionIfNeeded(@WindowManager.TransitionType int type, - @Nullable WindowContainer trigger) { - return requestTransitionIfNeeded(type, 0 /* flags */, trigger); + @NonNull WindowContainer trigger) { + return requestTransitionIfNeeded(type, 0 /* flags */, trigger, trigger /* readyGroupRef */); } /** @@ -201,8 +201,10 @@ class TransitionController { */ @Nullable Transition requestTransitionIfNeeded(@WindowManager.TransitionType int type, - @WindowManager.TransitionFlags int flags, @Nullable WindowContainer trigger) { - return requestTransitionIfNeeded(type, flags, trigger, null /* remote */); + @WindowManager.TransitionFlags int flags, @Nullable WindowContainer trigger, + @NonNull WindowContainer readyGroupRef) { + return requestTransitionIfNeeded(type, flags, trigger, readyGroupRef, + null /* remoteTransition */); } private static boolean isExistenceType(@WindowManager.TransitionType int type) { @@ -213,19 +215,20 @@ class TransitionController { * If a transition isn't requested yet, creates one and asks the TransitionPlayer (Shell) to * start it. Collection can start immediately. * @param trigger if non-null, this is the first container that will be collected + * @param readyGroupRef Used to identify which ready-group this request is for. * @return the created transition if created or null otherwise. */ @Nullable Transition requestTransitionIfNeeded(@WindowManager.TransitionType int type, @WindowManager.TransitionFlags int flags, @Nullable WindowContainer trigger, - @Nullable IRemoteTransition remoteTransition) { + @NonNull WindowContainer readyGroupRef, @Nullable IRemoteTransition remoteTransition) { if (mTransitionPlayer == null) { return null; } Transition newTransition = null; if (isCollecting()) { // Make the collecting transition wait until this request is ready. - mCollectingTransition.setReady(false); + mCollectingTransition.setReady(readyGroupRef, false); } else { newTransition = requestStartTransition(createTransition(type, flags), trigger != null ? trigger.asTask() : null, remoteTransition); @@ -280,14 +283,14 @@ class TransitionController { } /** @see Transition#setReady */ - void setReady(boolean ready) { + void setReady(WindowContainer wc, boolean ready) { if (mCollectingTransition == null) return; - mCollectingTransition.setReady(ready); + mCollectingTransition.setReady(wc, ready); } /** @see Transition#setReady */ - void setReady() { - setReady(true); + void setReady(WindowContainer wc) { + setReady(wc, true); } /** @see Transition#finishTransition */ diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 913c5e56109b2..ffee0b795aec3 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -851,7 +851,8 @@ class WindowContainer extends ConfigurationContainer< } boolean isAttached() { - return getDisplayArea() != null; + WindowContainer parent = getParent(); + return parent != null && parent.isAttached(); } void setWaitingForDrawnIfResizingChanged() { diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 53e02e5395a50..a4db2dc614016 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -8123,7 +8123,11 @@ public class WindowManagerService extends IWindowManager.Stub boolean isAnimating = mAnimator.isAnimationScheduled() || mRoot.isAnimating(TRANSITION | CHILDREN, ANIMATION_TYPE_ALL); if (!isAnimating) { - break; + // isAnimating is a legacy transition query and will be removed, so also add + // a check for whether this is in a shell-transition when not using legacy. + if (!mAtmService.getTransitionController().inTransition()) { + break; + } } long startTime = System.currentTimeMillis(); try { diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index eeb83c66ce2bf..bb634908174c3 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -227,7 +227,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } applyTransaction(t, -1 /*syncId*/, transition, caller); if (needsSetReady) { - transition.setReady(); + transition.setAllReady(); } return transition; }