1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.wm;
18 
19 import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS;
20 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
21 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
22 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
23 import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
24 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
25 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
26 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
27 import static android.view.Display.DEFAULT_DISPLAY;
28 import static android.view.Display.INVALID_DISPLAY;
29 import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION;
30 import static android.view.WindowManager.KEYGUARD_VISIBILITY_TRANSIT_FLAGS;
31 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
32 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
33 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
34 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
35 import static android.view.WindowManager.TRANSIT_CHANGE;
36 import static android.view.WindowManager.TRANSIT_CLOSE;
37 import static android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS;
38 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
39 import static android.view.WindowManager.TRANSIT_OPEN;
40 import static android.view.WindowManager.TRANSIT_TO_BACK;
41 import static android.view.WindowManager.TRANSIT_TO_FRONT;
42 import static android.view.WindowManager.TransitionFlags;
43 import static android.view.WindowManager.TransitionType;
44 import static android.view.WindowManager.transitTypeToString;
45 import static android.window.TaskFragmentAnimationParams.DEFAULT_ANIMATION_BACKGROUND_COLOR;
46 import static android.window.TransitionInfo.AnimationOptions;
47 import static android.window.TransitionInfo.FLAGS_IS_OCCLUDED_NO_ANIMATION;
48 import static android.window.TransitionInfo.FLAG_CONFIG_AT_END;
49 import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS;
50 import static android.window.TransitionInfo.FLAG_FILLS_TASK;
51 import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
52 import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
53 import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
54 import static android.window.TransitionInfo.FLAG_IS_INPUT_METHOD;
55 import static android.window.TransitionInfo.FLAG_IS_OCCLUDED;
56 import static android.window.TransitionInfo.FLAG_IS_VOICE_INTERACTION;
57 import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
58 import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
59 import static android.window.TransitionInfo.FLAG_NO_ANIMATION;
60 import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
61 import static android.window.TransitionInfo.FLAG_TASK_LAUNCHING_BEHIND;
62 import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
63 import static android.window.TransitionInfo.FLAG_WILL_IME_SHOWN;
64 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_PENDING_INTENT;
65 
66 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
67 import static com.android.server.wm.ActivityRecord.State.RESUMED;
68 import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_RECENTS_ANIM;
69 import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_SPLASH_SCREEN;
70 import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_WINDOWS_DRAWN;
71 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_PREDICT_BACK;
72 import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
73 import static com.android.server.wm.WindowState.BLAST_TIMEOUT_DURATION;
74 
75 import android.annotation.IntDef;
76 import android.annotation.NonNull;
77 import android.annotation.Nullable;
78 import android.app.ActivityManager;
79 import android.app.ActivityOptions;
80 import android.app.IApplicationThread;
81 import android.content.pm.ActivityInfo;
82 import android.graphics.Point;
83 import android.graphics.Rect;
84 import android.hardware.HardwareBuffer;
85 import android.os.Binder;
86 import android.os.Bundle;
87 import android.os.IBinder;
88 import android.os.IRemoteCallback;
89 import android.os.Looper;
90 import android.os.RemoteException;
91 import android.os.SystemClock;
92 import android.os.Trace;
93 import android.util.ArrayMap;
94 import android.util.ArraySet;
95 import android.util.Slog;
96 import android.util.SparseArray;
97 import android.view.Display;
98 import android.view.SurfaceControl;
99 import android.view.WindowManager;
100 import android.window.ScreenCapture;
101 import android.window.TaskFragmentAnimationParams;
102 import android.window.TransitionInfo;
103 import android.window.WindowContainerTransaction;
104 
105 import com.android.internal.annotations.VisibleForTesting;
106 import com.android.internal.graphics.ColorUtils;
107 import com.android.internal.policy.TransitionAnimation;
108 import com.android.internal.protolog.ProtoLogGroup;
109 import com.android.internal.protolog.common.ProtoLog;
110 import com.android.internal.util.function.pooled.PooledLambda;
111 import com.android.server.inputmethod.InputMethodManagerInternal;
112 import com.android.server.statusbar.StatusBarManagerInternal;
113 import com.android.window.flags.Flags;
114 
115 import java.lang.annotation.Retention;
116 import java.lang.annotation.RetentionPolicy;
117 import java.lang.ref.WeakReference;
118 import java.util.ArrayList;
119 import java.util.List;
120 import java.util.Objects;
121 import java.util.function.Predicate;
122 
123 /**
124  * Represents a logical transition. This keeps track of all the changes associated with a logical
125  * WM state -> state transition.
126  * @see TransitionController
127  *
128  * In addition to tracking individual container changes, this also tracks ordering-changes (just
129  * on-top for now). However, since order is a "global" property, the mechanics of order-change
130  * detection/reporting is non-trivial when transitions are collecting in parallel. See
131  * {@link #collectOrderChanges} for more details.
132  */
133 class Transition implements BLASTSyncEngine.TransactionReadyListener {
134     private static final String TAG = "Transition";
135     private static final String TRACE_NAME_PLAY_TRANSITION = "playing";
136 
137     /** The default package for resources */
138     private static final String DEFAULT_PACKAGE = "android";
139 
140     /** The transition has been created but isn't collecting yet. */
141     private static final int STATE_PENDING = -1;
142 
143     /** The transition has been created and is collecting, but hasn't formally started. */
144     private static final int STATE_COLLECTING = 0;
145 
146     /**
147      * The transition has formally started. It is still collecting but will stop once all
148      * participants are ready to animate (finished drawing).
149      */
150     private static final int STATE_STARTED = 1;
151 
152     /**
153      * This transition is currently playing its animation and can no longer collect or be changed.
154      */
155     private static final int STATE_PLAYING = 2;
156 
157     /**
158      * This transition is aborting or has aborted. No animation will play nor will anything get
159      * sent to the player.
160      */
161     private static final int STATE_ABORT = 3;
162 
163     /**
164      * This transition has finished playing successfully.
165      */
166     private static final int STATE_FINISHED = 4;
167 
168     @IntDef(prefix = { "STATE_" }, value = {
169             STATE_PENDING,
170             STATE_COLLECTING,
171             STATE_STARTED,
172             STATE_PLAYING,
173             STATE_ABORT,
174             STATE_FINISHED
175     })
176     @Retention(RetentionPolicy.SOURCE)
177     @interface TransitionState {}
178 
179     final @TransitionType int mType;
180     private int mSyncId = -1;
181     private @TransitionFlags int mFlags;
182     private final TransitionController mController;
183     private final BLASTSyncEngine mSyncEngine;
184     private final Token mToken;
185 
186     private @Nullable ActivityRecord mPipActivity;
187 
188     /** Only use for clean-up after binder death! */
189     private SurfaceControl.Transaction mStartTransaction = null;
190     private SurfaceControl.Transaction mFinishTransaction = null;
191 
192     /** Used for failsafe clean-up to prevent leaks due to misbehaving player impls. */
193     private SurfaceControl.Transaction mCleanupTransaction = null;
194 
195     /**
196      * Contains change infos for both participants and all remote-animatable ancestors. The
197      * ancestors can be the promotion candidates so their start-states need to be captured.
198      * @see #getAnimatableParent
199      */
200     final ArrayMap<WindowContainer, ChangeInfo> mChanges = new ArrayMap<>();
201 
202     /** The collected participants in the transition. */
203     final ArraySet<WindowContainer> mParticipants = new ArraySet<>();
204 
205     /** The final animation targets derived from participants after promotion. */
206     ArrayList<ChangeInfo> mTargets;
207 
208     /** The displays that this transition is running on. */
209     private final ArrayList<DisplayContent> mTargetDisplays = new ArrayList<>();
210 
211     /**
212      * The (non alwaysOnTop) tasks which were on-top of their display before the transition. If
213      * tasks are nested, all the tasks that are parents of the on-top task are also included.
214      */
215     private final ArrayList<Task> mOnTopTasksStart = new ArrayList<>();
216 
217     /**
218      * The (non alwaysOnTop) tasks which were on-top of their display when this transition became
219      * ready (via setReady, not animation-ready).
220      */
221     private final ArrayList<Task> mOnTopTasksAtReady = new ArrayList<>();
222 
223     /**
224      * Set of participating windowtokens (activity/wallpaper) which are visible at the end of
225      * the transition animation.
226      */
227     private final ArraySet<WindowToken> mVisibleAtTransitionEndTokens = new ArraySet<>();
228 
229     /**
230      * Set of transient activities (lifecycle initially tied to this transition) and their
231      * restore-below tasks.
232      */
233     private ArrayMap<ActivityRecord, Task> mTransientLaunches = null;
234 
235     /**
236      * The tasks that may be occluded by the transient activity. Assume the task stack is
237      * [Home, A(opaque), B(opaque), C(translucent)] (bottom to top), then A is the restore-below
238      * task, and [B, C] are the transient-hide tasks.
239      */
240     private ArrayList<Task> mTransientHideTasks;
241 
242     @VisibleForTesting
243     ArrayList<Runnable> mTransactionCompletedListeners = null;
244 
245     private ArrayList<Runnable> mTransitionEndedListeners = null;
246 
247     /** Custom activity-level animation options and callbacks. */
248     private AnimationOptions mOverrideOptions;
249 
250     private IRemoteCallback mClientAnimationStartCallback = null;
251     private IRemoteCallback mClientAnimationFinishCallback = null;
252 
253     private @TransitionState int mState = STATE_PENDING;
254     private final ReadyTrackerOld mReadyTrackerOld = new ReadyTrackerOld();
255     final ReadyTracker mReadyTracker = new ReadyTracker(this);
256 
257     private int mRecentsDisplayId = INVALID_DISPLAY;
258 
259     /** The delay for light bar appearance animation. */
260     long mStatusBarTransitionDelay;
261 
262     /** @see #setCanPipOnFinish */
263     private boolean mCanPipOnFinish = true;
264 
265     private boolean mIsSeamlessRotation = false;
266     private IContainerFreezer mContainerFreezer = null;
267 
268     /**
269      * {@code true} if some other operation may have caused the originally-recorded state (in
270      * mChanges) to be dirty. This is usually due to finishTransition being called mid-collect;
271      * and, the reason that finish can alter the "start" state of other transitions is because
272      * setVisible(false) is deferred until then.
273      * Instead of adding this conditional, we could re-check always; but, this situation isn't
274      * common so it'd be wasted work.
275      */
276     boolean mPriorVisibilityMightBeDirty = false;
277 
278     final TransitionController.Logger mLogger = new TransitionController.Logger();
279 
280     /** Whether this transition was forced to play early (eg for a SLEEP signal). */
281     private boolean mForcePlaying = false;
282 
283     /**
284      * {@code false} if this transition runs purely in WMCore (meaning Shell is completely unaware
285      * of it). Currently, this happens before the display is ready since nothing can be seen yet.
286      */
287     boolean mIsPlayerEnabled = true;
288 
289     /** This transition doesn't run in parallel. */
290     static final int PARALLEL_TYPE_NONE = 0;
291 
292     /** Any 2 transitions of this type can run in parallel with each other. Used for testing. */
293     static final int PARALLEL_TYPE_MUTUAL = 1;
294 
295     /** This is a recents transition. */
296     static final int PARALLEL_TYPE_RECENTS = 2;
297 
298 
299     @IntDef(prefix = { "PARALLEL_TYPE_" }, value = {
300             PARALLEL_TYPE_NONE,
301             PARALLEL_TYPE_MUTUAL,
302             PARALLEL_TYPE_RECENTS
303     })
304     @Retention(RetentionPolicy.SOURCE)
305     @interface ParallelType {}
306 
307     /**
308      * What category of parallel-collect support this transition has. The value of this is used
309      * by {@link TransitionController} to determine which transitions can collect in parallel. If
310      * a transition can collect in parallel, it means that it will start collecting as soon as the
311      * prior collecting transition is {@link #isPopulated}. This is a shortcut for supporting
312      * a couple specific situations before we have full-fledged support for parallel transitions.
313      */
314     @ParallelType int mParallelCollectType = PARALLEL_TYPE_NONE;
315 
316     /**
317      * A "Track" is a set of animations which must cooperate with each other to play smoothly. If
318      * animations can play independently of each other, then they can be in different tracks. If
319      * a transition must cooperate with transitions in >1 other track, then it must be marked
320      * FLAG_SYNC and it will end-up flushing all animations before it starts.
321      */
322     int mAnimationTrack = 0;
323 
324     /**
325      * List of activities whose configurations are sent to the client at the end of the transition
326      * instead of immediately when the configuration changes.
327      */
328     ArrayList<ActivityRecord> mConfigAtEndActivities = null;
329 
330     @VisibleForTesting
Transition(@ransitionType int type, @TransitionFlags int flags, TransitionController controller, BLASTSyncEngine syncEngine)331     Transition(@TransitionType int type, @TransitionFlags int flags,
332             TransitionController controller, BLASTSyncEngine syncEngine) {
333         mType = type;
334         mFlags = flags;
335         mController = controller;
336         mSyncEngine = syncEngine;
337         mToken = new Token(this);
338 
339         mLogger.mCreateWallTimeMs = System.currentTimeMillis();
340         mLogger.mCreateTimeNs = SystemClock.elapsedRealtimeNanos();
341     }
342 
343     @Nullable
fromBinder(@ullable IBinder token)344     static Transition fromBinder(@Nullable IBinder token) {
345         if (token == null) return null;
346         try {
347             return ((Token) token).mTransition.get();
348         } catch (ClassCastException e) {
349             Slog.w(TAG, "Invalid transition token: " + token, e);
350             return null;
351         }
352     }
353 
354     @NonNull
getToken()355     IBinder getToken() {
356         return mToken;
357     }
358 
addFlag(int flag)359     void addFlag(int flag) {
360         mFlags |= flag;
361     }
362 
calcParallelCollectType(WindowContainerTransaction wct)363     void calcParallelCollectType(WindowContainerTransaction wct) {
364         for (int i = 0; i < wct.getHierarchyOps().size(); ++i) {
365             final WindowContainerTransaction.HierarchyOp hop = wct.getHierarchyOps().get(i);
366             if (hop.getType() != HIERARCHY_OP_TYPE_PENDING_INTENT) continue;
367             final Bundle b = hop.getLaunchOptions();
368             if (b == null || b.isEmpty()) continue;
369             final boolean transientLaunch = b.getBoolean(ActivityOptions.KEY_TRANSIENT_LAUNCH);
370             if (transientLaunch) {
371                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
372                         "Starting a Recents transition which can be parallel.");
373                 mParallelCollectType = PARALLEL_TYPE_RECENTS;
374             }
375         }
376     }
377 
378     /** Records an activity as transient-launch. This activity must be already collected. */
setTransientLaunch(@onNull ActivityRecord activity, @Nullable Task restoreBelow)379     void setTransientLaunch(@NonNull ActivityRecord activity, @Nullable Task restoreBelow) {
380         if (mTransientLaunches == null) {
381             mTransientLaunches = new ArrayMap<>();
382             mTransientHideTasks = new ArrayList<>();
383         }
384         mTransientLaunches.put(activity, restoreBelow);
385         setTransientLaunchToChanges(activity);
386 
387         final Task transientRootTask = activity.getRootTask();
388         final WindowContainer<?> parent = restoreBelow != null ? restoreBelow.getParent()
389                 : (transientRootTask != null ? transientRootTask.getParent() : null);
390         if (parent != null) {
391             // Collect all visible tasks which can be occluded by the transient activity to
392             // make sure they are in the participants so their visibilities can be updated when
393             // finishing transition.
394             parent.forAllTasks(t -> {
395                 // Skip transient-launch task
396                 if (t == transientRootTask) return false;
397                 if (t.isVisibleRequested() && !t.isAlwaysOnTop()) {
398                     if (t.isRootTask()) {
399                         mTransientHideTasks.add(t);
400                     }
401                     if (t.isLeafTask()) {
402                         collect(t);
403                     }
404                 }
405                 return restoreBelow != null
406                         // Stop at the restoreBelow task
407                         ? t == restoreBelow
408                         // Or stop at the last visible task if no restore-below (new task)
409                         : (t.isRootTask() && t.fillsParent());
410             });
411             // Add FLAG_ABOVE_TRANSIENT_LAUNCH to the tree of transient-hide tasks,
412             // so ChangeInfo#hasChanged() can return true to report the transition info.
413             for (int i = mChanges.size() - 1; i >= 0; --i) {
414                 updateTransientFlags(mChanges.valueAt(i));
415             }
416         }
417         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Transition %d: Set %s as "
418                 + "transient-launch", mSyncId, activity);
419     }
420 
421     /** @return whether `wc` is a descendent of a transient-hide window. */
isInTransientHide(@onNull WindowContainer wc)422     boolean isInTransientHide(@NonNull WindowContainer wc) {
423         if (mTransientHideTasks == null) return false;
424         for (int i = mTransientHideTasks.size() - 1; i >= 0; --i) {
425             final Task task = mTransientHideTasks.get(i);
426             if (wc == task || wc.isDescendantOf(task)) {
427                 return true;
428             }
429         }
430         return false;
431     }
432 
433     /** Returns {@code true} if the task should keep visible if this is a transient transition. */
isTransientVisible(@onNull Task task)434     boolean isTransientVisible(@NonNull Task task) {
435         if (mTransientLaunches == null) return false;
436         int occludedCount = 0;
437         final int numTransient = mTransientLaunches.size();
438         for (int i = numTransient - 1; i >= 0; --i) {
439             final Task transientRoot = mTransientLaunches.keyAt(i).getRootTask();
440             if (transientRoot == null) continue;
441             final WindowContainer<?> rootParent = transientRoot.getParent();
442             if (rootParent == null || rootParent.getTopChild() == transientRoot) continue;
443             final ActivityRecord topOpaque = mController.mAtm.mTaskSupervisor.mOpaqueActivityHelper
444                     .getOpaqueActivity(rootParent, true /* ignoringKeyguard */);
445             if (transientRoot.compareTo(topOpaque.getRootTask()) < 0) {
446                 occludedCount++;
447             }
448         }
449         if (occludedCount == numTransient) {
450             for (int i = mTransientLaunches.size() - 1; i >= 0; --i) {
451                 if (mTransientLaunches.keyAt(i).isDescendantOf(task)) {
452                     // Keep transient activity visible until transition finished, so it won't pause
453                     // with transient-hide tasks that may delay resuming the next top.
454                     return true;
455                 }
456             }
457             // Let transient-hide activities pause before transition is finished.
458             return false;
459         }
460         return isInTransientHide(task);
461     }
462 
canApplyDim(@onNull Task task)463     boolean canApplyDim(@NonNull Task task) {
464         if (mTransientLaunches == null) return true;
465         final Dimmer dimmer = task.getDimmer();
466         if (dimmer == null) {
467             return false;
468         }
469         if (dimmer.getHost().asTask() != null) {
470             // Always allow to dim if the host only affects its task.
471             return true;
472         }
473         // The dimmer host of a translucent task can be a display, then it is not in transient-hide.
474         for (int i = mTransientLaunches.size() - 1; i >= 0; --i) {
475             // The transient task is usually the task of recents/home activity.
476             final Task transientTask = mTransientLaunches.keyAt(i).getTask();
477             if (transientTask != null && transientTask.canAffectSystemUiFlags()) {
478                 // It usually means that the recents animation has moved the transient-hide task
479                 // an noticeable distance, then the display level dimmer should not show.
480                 return false;
481             }
482         }
483         return true;
484     }
485 
hasTransientLaunch()486     boolean hasTransientLaunch() {
487         return mTransientLaunches != null && !mTransientLaunches.isEmpty();
488     }
489 
isTransientLaunch(@onNull ActivityRecord activity)490     boolean isTransientLaunch(@NonNull ActivityRecord activity) {
491         return mTransientLaunches != null && mTransientLaunches.containsKey(activity);
492     }
493 
getTransientLaunchRestoreTarget(@onNull WindowContainer container)494     Task getTransientLaunchRestoreTarget(@NonNull WindowContainer container) {
495         if (mTransientLaunches == null) return null;
496         for (int i = 0; i < mTransientLaunches.size(); ++i) {
497             if (mTransientLaunches.keyAt(i).isDescendantOf(container)) {
498                 return mTransientLaunches.valueAt(i);
499             }
500         }
501         return null;
502     }
503 
isOnDisplay(@onNull DisplayContent dc)504     boolean isOnDisplay(@NonNull DisplayContent dc) {
505         return mTargetDisplays.contains(dc);
506     }
507 
setConfigAtEnd(@onNull WindowContainer<?> wc)508     void setConfigAtEnd(@NonNull WindowContainer<?> wc) {
509         wc.forAllActivities(ar -> {
510             if (!ar.isVisible() || !ar.isVisibleRequested()) return;
511             if (mConfigAtEndActivities == null) {
512                 mConfigAtEndActivities = new ArrayList<>();
513             }
514             if (mConfigAtEndActivities.contains(ar)) {
515                 return;
516             }
517             mConfigAtEndActivities.add(ar);
518             ar.pauseConfigurationDispatch();
519             snapshotStartState(ar);
520             mChanges.get(ar).mFlags |= ChangeInfo.FLAG_CHANGE_CONFIG_AT_END;
521         });
522         snapshotStartState(wc);
523         mChanges.get(wc).mFlags |= ChangeInfo.FLAG_CHANGE_CONFIG_AT_END;
524     }
525 
526     /** Set a transition to be a seamless-rotation. */
setSeamlessRotation(@onNull WindowContainer wc)527     void setSeamlessRotation(@NonNull WindowContainer wc) {
528         final ChangeInfo info = mChanges.get(wc);
529         if (info == null) return;
530         info.mFlags = info.mFlags | ChangeInfo.FLAG_SEAMLESS_ROTATION;
531         onSeamlessRotating(wc.getDisplayContent());
532     }
533 
534     /**
535      * Called when it's been determined that this is transition is a seamless rotation. This should
536      * be called before any WM changes have happened.
537      */
onSeamlessRotating(@onNull DisplayContent dc)538     void onSeamlessRotating(@NonNull DisplayContent dc) {
539         // Don't need to do anything special if everything is using BLAST sync already.
540         if (mSyncEngine.getSyncSet(mSyncId).mSyncMethod == BLASTSyncEngine.METHOD_BLAST) return;
541         if (mContainerFreezer == null) {
542             mContainerFreezer = new ScreenshotFreezer();
543         }
544         final WindowState top = dc.getDisplayPolicy().getTopFullscreenOpaqueWindow();
545         if (top != null) {
546             mIsSeamlessRotation = true;
547             top.mSyncMethodOverride = BLASTSyncEngine.METHOD_BLAST;
548             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Override sync-method for %s "
549                     + "because seamless rotating", top.getName());
550         }
551     }
552 
553     /**
554      * Set the pip-able activity participating in this transition.
555      * @param pipActivity activity about to enter pip
556      */
setPipActivity(@ullable ActivityRecord pipActivity)557     void setPipActivity(@Nullable ActivityRecord pipActivity) {
558         mPipActivity = pipActivity;
559     }
560 
561     /**
562      * @return pip-able activity participating in this transition.
563      */
getPipActivity()564     @Nullable ActivityRecord getPipActivity() {
565         return mPipActivity;
566     }
567 
568     /**
569      * Only set flag to the parent tasks and activity itself.
570      */
setTransientLaunchToChanges(@onNull WindowContainer wc)571     private void setTransientLaunchToChanges(@NonNull WindowContainer wc) {
572         for (WindowContainer curr = wc; curr != null && mChanges.containsKey(curr);
573                 curr = curr.getParent()) {
574             if (curr.asTask() == null && curr.asActivityRecord() == null) {
575                 return;
576             }
577             final ChangeInfo info = mChanges.get(curr);
578             info.mFlags = info.mFlags | ChangeInfo.FLAG_TRANSIENT_LAUNCH;
579         }
580     }
581 
582     /** Only for testing. */
setContainerFreezer(IContainerFreezer freezer)583     void setContainerFreezer(IContainerFreezer freezer) {
584         mContainerFreezer = freezer;
585     }
586 
587     @TransitionState
getState()588     int getState() {
589         return mState;
590     }
591 
getSyncId()592     int getSyncId() {
593         return mSyncId;
594     }
595 
596     @TransitionFlags
getFlags()597     int getFlags() {
598         return mFlags;
599     }
600 
601     @VisibleForTesting
getStartTransaction()602     SurfaceControl.Transaction getStartTransaction() {
603         return mStartTransaction;
604     }
605 
606     @VisibleForTesting
getFinishTransaction()607     SurfaceControl.Transaction getFinishTransaction() {
608         return mFinishTransaction;
609     }
610 
isPending()611     boolean isPending() {
612         return mState == STATE_PENDING;
613     }
614 
isCollecting()615     boolean isCollecting() {
616         return mState == STATE_COLLECTING || mState == STATE_STARTED;
617     }
618 
isAborted()619     boolean isAborted() {
620         return mState == STATE_ABORT;
621     }
622 
isStarted()623     boolean isStarted() {
624         return mState == STATE_STARTED;
625     }
626 
isPlaying()627     boolean isPlaying() {
628         return mState == STATE_PLAYING;
629     }
630 
isFinished()631     boolean isFinished() {
632         return mState == STATE_FINISHED;
633     }
634 
635     /** Starts collecting phase. Once this starts, all relevant surface operations are sync. */
startCollecting(long timeoutMs)636     void startCollecting(long timeoutMs) {
637         if (mState != STATE_PENDING) {
638             throw new IllegalStateException("Attempting to re-use a transition");
639         }
640         mState = STATE_COLLECTING;
641         mSyncId = mSyncEngine.startSyncSet(this, timeoutMs,
642                 TAG + "-" + transitTypeToString(mType),
643                 mParallelCollectType != PARALLEL_TYPE_NONE);
644         mSyncEngine.setSyncMethod(mSyncId, TransitionController.SYNC_METHOD);
645 
646         mLogger.mSyncId = mSyncId;
647         mLogger.mCollectTimeNs = SystemClock.elapsedRealtimeNanos();
648     }
649 
650     /**
651      * Formally starts the transition. Participants can be collected before this is started,
652      * but this won't consider itself ready until started -- even if all the participants have
653      * drawn.
654      */
start()655     void start() {
656         if (mState < STATE_COLLECTING) {
657             throw new IllegalStateException("Can't start Transition which isn't collecting.");
658         } else if (mState >= STATE_STARTED) {
659             Slog.w(TAG, "Transition already started id=" + mSyncId + " state=" + mState);
660             // The transition may be aborted (STATE_ABORT) or timed out (STATE_PLAYING by
661             // SyncGroup#finishNow), so do not revert the state to STATE_STARTED.
662             return;
663         }
664         mState = STATE_STARTED;
665         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Starting Transition %d",
666                 mSyncId);
667         applyReady();
668 
669         mLogger.mStartTimeNs = SystemClock.elapsedRealtimeNanos();
670 
671         mController.updateAnimatingState();
672     }
673 
674     /**
675      * Adds wc to set of WindowContainers participating in this transition.
676      */
collect(@onNull WindowContainer wc)677     void collect(@NonNull WindowContainer wc) {
678         if (mState < STATE_COLLECTING) {
679             throw new IllegalStateException("Transition hasn't started collecting.");
680         }
681         if (!isCollecting()) {
682             // Too late, transition already started playing, so don't collect.
683             return;
684         }
685         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Collecting in transition %d: %s",
686                 mSyncId, wc);
687         // Snapshot before checking if this is a participant in case it has been re-parented.
688         snapshotStartState(getAnimatableParent(wc));
689         if (mParticipants.contains(wc)) return;
690         // Transient-hide may be hidden later, so no need to request redraw.
691         if (!isInTransientHide(wc)) {
692             mSyncEngine.addToSyncSet(mSyncId, wc);
693         }
694         if (wc.asWindowToken() != null && wc.asWindowToken().mRoundedCornerOverlay) {
695             // Only need to sync the transaction (SyncSet) without ChangeInfo because cutout and
696             // rounded corner overlay never need animations. Especially their surfaces may be put
697             // in root (null, see WindowToken#makeSurface()) that cannot reparent.
698             return;
699         }
700         ChangeInfo info = mChanges.get(wc);
701         if (info == null) {
702             info = new ChangeInfo(wc);
703             updateTransientFlags(info);
704             mChanges.put(wc, info);
705         }
706         mParticipants.add(wc);
707         recordDisplay(wc.getDisplayContent());
708         if (info.mShowWallpaper) {
709             // Collect the wallpaper token (for isWallpaper(wc)) so it is part of the sync set.
710             wc.mDisplayContent.mWallpaperController.collectTopWallpapers(this);
711         }
712     }
713 
714     /** "snapshot" `wc` and all its parents (as potential promotion targets). */
snapshotStartState(@onNull WindowContainer<?> wc)715     private void snapshotStartState(@NonNull WindowContainer<?> wc) {
716         for (WindowContainer<?> curr = wc;
717                 curr != null && !mChanges.containsKey(curr);
718                 curr = getAnimatableParent(curr)) {
719             final ChangeInfo info = new ChangeInfo(curr);
720             updateTransientFlags(info);
721             mChanges.put(curr, info);
722             if (isReadyGroup(curr)) {
723                 mReadyTrackerOld.addGroup(curr);
724                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Creating Ready-group for"
725                         + " Transition %d with root=%s", mSyncId, curr);
726             }
727         }
728     }
729 
updateTransientFlags(@onNull ChangeInfo info)730     private void updateTransientFlags(@NonNull ChangeInfo info) {
731         final WindowContainer<?> wc = info.mContainer;
732         // Only look at tasks, taskfragments, or activities
733         if (wc.asTaskFragment() == null && wc.asActivityRecord() == null) return;
734         if (!isInTransientHide(wc)) return;
735         info.mFlags |= ChangeInfo.FLAG_ABOVE_TRANSIENT_LAUNCH;
736     }
737 
recordDisplay(DisplayContent dc)738     private void recordDisplay(DisplayContent dc) {
739         if (dc == null || mTargetDisplays.contains(dc)) return;
740         mTargetDisplays.add(dc);
741         addOnTopTasks(dc, mOnTopTasksStart);
742         // Handle the case {transition.start(); applyTransaction(wct);} that the animating state
743         // is set before collecting participants.
744         if (mController.isAnimating()) {
745             dc.enableHighPerfTransition(true);
746         }
747     }
748 
749     /**
750      * Records information about the initial task order. This does NOT collect anything. Call this
751      * before any ordering changes *could* occur, but it is not known yet if it will occur.
752      */
recordTaskOrder(WindowContainer from)753     void recordTaskOrder(WindowContainer from) {
754         recordDisplay(from.getDisplayContent());
755     }
756 
757     /** Adds the top non-alwaysOnTop tasks within `task` to `out`. */
addOnTopTasks(Task task, ArrayList<Task> out)758     private static void addOnTopTasks(Task task, ArrayList<Task> out) {
759         for (int i = task.getChildCount() - 1; i >= 0; --i) {
760             final Task child = task.getChildAt(i).asTask();
761             if (child == null) return;
762             if (child.getWindowConfiguration().isAlwaysOnTop()) continue;
763             out.add(child);
764             addOnTopTasks(child, out);
765             break;
766         }
767     }
768 
769     /** Get the top non-alwaysOnTop leaf task on the display `dc`. */
addOnTopTasks(DisplayContent dc, ArrayList<Task> out)770     private static void addOnTopTasks(DisplayContent dc, ArrayList<Task> out) {
771         final Task topNotAlwaysOnTop = dc.getRootTask(
772                 t -> !t.getWindowConfiguration().isAlwaysOnTop());
773         if (topNotAlwaysOnTop == null) return;
774         out.add(topNotAlwaysOnTop);
775         addOnTopTasks(topNotAlwaysOnTop, out);
776     }
777 
778     /**
779      * Records wc as changing its state of existence during this transition. For example, a new
780      * task is considered an existence change while moving a task to front is not. wc is added
781      * to the collection set. Note: Existence is NOT a promotable characteristic.
782      *
783      * This must be explicitly recorded because there are o number of situations where the actual
784      * hierarchy operations don't align with the intent (eg. re-using a task with a new activity
785      * or waiting until after the animation to close).
786      */
collectExistenceChange(@onNull WindowContainer wc)787     void collectExistenceChange(@NonNull WindowContainer wc) {
788         if (mState >= STATE_PLAYING) {
789             // Too late to collect. Don't check too-early here since `collect` will check that.
790             return;
791         }
792         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Existence Changed in transition %d:"
793                 + " %s", mSyncId, wc);
794         collect(wc);
795         mChanges.get(wc).mExistenceChanged = true;
796     }
797 
798     /**
799      * Records that a particular container is changing visibly (ie. something about it is changing
800      * while it remains visible). This only effects windows that are already in the collecting
801      * transition.
802      */
collectVisibleChange(WindowContainer wc)803     void collectVisibleChange(WindowContainer wc) {
804         if (mSyncEngine.getSyncSet(mSyncId).mSyncMethod == BLASTSyncEngine.METHOD_BLAST) {
805             // All windows are synced already.
806             return;
807         }
808         if (wc.mDisplayContent == null || !isInTransition(wc)) return;
809         if (!wc.mDisplayContent.getDisplayPolicy().isScreenOnFully()
810                 || wc.mDisplayContent.getDisplayInfo().state == Display.STATE_OFF) {
811             mFlags |= WindowManager.TRANSIT_FLAG_INVISIBLE;
812             return;
813         }
814         // Activity doesn't need to capture snapshot if the starting window has associated to task.
815         if (wc.asActivityRecord() != null) {
816             final ActivityRecord activityRecord = wc.asActivityRecord();
817             if (activityRecord.mStartingData != null
818                     && activityRecord.mStartingData.mAssociatedTask != null) {
819                 return;
820             }
821         }
822 
823         if (mContainerFreezer == null) {
824             mContainerFreezer = new ScreenshotFreezer();
825         }
826         Transition.ChangeInfo change = mChanges.get(wc);
827         if (change == null || !change.mVisible || !wc.isVisibleRequested()) return;
828         // Note: many more tests have already been done by caller.
829         mContainerFreezer.freeze(wc, change.mAbsoluteBounds);
830     }
831 
832     /**
833      * Records that a particular container has been reparented. This only effects windows that have
834      * already been collected in the transition. This should be called before reparenting because
835      * the old parent may be removed during reparenting, for example:
836      * {@link Task#shouldRemoveSelfOnLastChildRemoval}
837      */
collectReparentChange(@onNull WindowContainer wc, @NonNull WindowContainer newParent)838     void collectReparentChange(@NonNull WindowContainer wc, @NonNull WindowContainer newParent) {
839         if (!mChanges.containsKey(wc)) {
840             // #collectReparentChange() will be called when the window is reparented. Skip if it is
841             // a window that has not been collected, which means we don't care about this window for
842             // the current transition.
843             return;
844         }
845         final ChangeInfo change = mChanges.get(wc);
846         // Use the current common ancestor if there are multiple reparent, and the original parent
847         // has been detached. Otherwise, use the original parent before the transition.
848         final WindowContainer prevParent =
849                 change.mStartParent == null || change.mStartParent.isAttached()
850                         ? change.mStartParent
851                         : change.mCommonAncestor;
852         if (prevParent == null || !prevParent.isAttached()) {
853             Slog.w(TAG, "Trying to collect reparenting of a window after the previous parent has"
854                     + " been detached: " + wc);
855             return;
856         }
857         if (prevParent == newParent) {
858             Slog.w(TAG, "Trying to collect reparenting of a window that has not been reparented: "
859                     + wc);
860             return;
861         }
862         if (!newParent.isAttached()) {
863             Slog.w(TAG, "Trying to collect reparenting of a window that is not attached after"
864                     + " reparenting: " + wc);
865             return;
866         }
867         WindowContainer ancestor = newParent;
868         while (prevParent != ancestor && !prevParent.isDescendantOf(ancestor)) {
869             ancestor = ancestor.getParent();
870         }
871         change.mCommonAncestor = ancestor;
872     }
873 
874     /**
875      * Collects a window container which will be removed or invisible.
876      */
collectClose(@onNull WindowContainer<?> wc)877     void collectClose(@NonNull WindowContainer<?> wc) {
878         if (wc.isVisibleRequested()) {
879             collectExistenceChange(wc);
880         } else {
881             // Removing a non-visible window doesn't require a transition, but if there is one
882             // collecting, this should be a member just in case.
883             collect(wc);
884         }
885     }
886 
887     /**
888      * @return {@code true} if `wc` is a participant or is a descendant of one.
889      */
isInTransition(WindowContainer wc)890     boolean isInTransition(WindowContainer wc) {
891         for (WindowContainer p = wc; p != null; p = p.getParent()) {
892             if (mParticipants.contains(p)) return true;
893         }
894         return false;
895     }
896 
897     /**
898      * Specifies configuration change explicitly for the window container, so it can be chosen as
899      * transition target. This is usually used with transition mode
900      * {@link android.view.WindowManager#TRANSIT_CHANGE}.
901      */
setKnownConfigChanges(WindowContainer<?> wc, @ActivityInfo.Config int changes)902     void setKnownConfigChanges(WindowContainer<?> wc, @ActivityInfo.Config int changes) {
903         final ChangeInfo changeInfo = mChanges.get(wc);
904         if (changeInfo != null) {
905             changeInfo.mKnownConfigChanges = changes;
906         }
907     }
908 
sendRemoteCallback(@ullable IRemoteCallback callback)909     private void sendRemoteCallback(@Nullable IRemoteCallback callback) {
910         if (callback == null) return;
911         mController.mAtm.mH.sendMessage(PooledLambda.obtainMessage(cb -> {
912             try {
913                 cb.sendResult(null);
914             } catch (RemoteException e) { }
915         }, callback));
916     }
917 
918     /**
919      * Set animation options for collecting transition by ActivityRecord.
920      * @param options AnimationOptions captured from ActivityOptions
921      */
setOverrideAnimation(@ullable AnimationOptions options, @Nullable IRemoteCallback startCallback, @Nullable IRemoteCallback finishCallback)922     void setOverrideAnimation(@Nullable AnimationOptions options,
923             @Nullable IRemoteCallback startCallback, @Nullable IRemoteCallback finishCallback) {
924         if (!isCollecting()) return;
925         mOverrideOptions = options;
926         sendRemoteCallback(mClientAnimationStartCallback);
927         mClientAnimationStartCallback = startCallback;
928         mClientAnimationFinishCallback = finishCallback;
929     }
930 
931     /**
932      * Call this when all known changes related to this transition have been applied. Until
933      * all participants have finished drawing, the transition can still collect participants.
934      *
935      * If this is called before the transition is started, it will be deferred until start.
936      *
937      * @param wc A reference point to determine which ready-group to update. For now, each display
938      *           has its own ready-group, so this is used to look-up which display to mark ready.
939      *           The transition will wait for all groups to be ready.
940      */
setReady(WindowContainer wc, boolean ready)941     void setReady(WindowContainer wc, boolean ready) {
942         if (!isCollecting() || mSyncId < 0) return;
943         mReadyTrackerOld.setReadyFrom(wc, ready);
944         applyReady();
945     }
946 
applyReady()947     private void applyReady() {
948         if (mState < STATE_STARTED) return;
949         final boolean ready;
950         if (mController.useFullReadyTracking()) {
951             ready = mReadyTracker.isReady();
952         } else {
953             ready = mReadyTrackerOld.allReady();
954         }
955         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
956                 "Set transition ready=%b %d", ready, mSyncId);
957         boolean changed = mSyncEngine.setReady(mSyncId, ready);
958         if (changed && ready) {
959             mLogger.mReadyTimeNs = SystemClock.elapsedRealtimeNanos();
960             mOnTopTasksAtReady.clear();
961             for (int i = 0; i < mTargetDisplays.size(); ++i) {
962                 addOnTopTasks(mTargetDisplays.get(i), mOnTopTasksAtReady);
963             }
964             mController.onTransitionPopulated(this);
965         }
966     }
967 
968     /**
969      * Sets all possible ready groups to ready.
970      * @see ReadyTrackerOld#setAllReady
971      */
setAllReady()972     void setAllReady() {
973         if (!isCollecting() || mSyncId < 0) return;
974         mReadyTrackerOld.setAllReady();
975         applyReady();
976     }
977 
978     @VisibleForTesting
allReady()979     boolean allReady() {
980         return mReadyTrackerOld.allReady();
981     }
982 
983     /** This transition has all of its expected participants. */
isPopulated()984     boolean isPopulated() {
985         return mState >= STATE_STARTED && mReadyTrackerOld.allReady();
986     }
987 
988     /**
989      * Populates `t` with instructions to reset surface transform of `change` so it matches
990      * the WM hierarchy. This "undoes" lingering state left by the animation.
991      */
resetSurfaceTransform(SurfaceControl.Transaction t, WindowContainer target, SurfaceControl targetLeash)992     private void resetSurfaceTransform(SurfaceControl.Transaction t, WindowContainer target,
993             SurfaceControl targetLeash) {
994         final Point tmpPos = new Point();
995         target.getRelativePosition(tmpPos);
996         t.setPosition(targetLeash, tmpPos.x, tmpPos.y);
997         // No need to clip the display in case seeing the clipped content when during the
998         // display rotation. No need to clip activities because they rely on clipping on
999         // task layers.
1000         if (target.asTaskFragment() == null) {
1001             t.setCrop(targetLeash, null /* crop */);
1002         } else {
1003             // Crop to the resolved override bounds.
1004             final Rect clipRect = target.getResolvedOverrideBounds();
1005             t.setWindowCrop(targetLeash, clipRect.width(), clipRect.height());
1006         }
1007         t.setMatrix(targetLeash, 1, 0, 0, 1);
1008         // The bounds sent to the transition is always a real bounds. This means we lose
1009         // information about "null" bounds (inheriting from parent). Core will fix-up
1010         // non-organized window surface bounds; however, since Core can't touch organized
1011         // surfaces, add the "inherit from parent" restoration here.
1012         if (target.isOrganized() && target.matchParentBounds()) {
1013             t.setWindowCrop(targetLeash, -1, -1);
1014         }
1015     }
1016 
1017     /**
1018      * Build a transaction that "resets" all the re-parenting and layer changes. This is
1019      * intended to be applied at the end of the transition but before the finish callback. This
1020      * needs to be passed/applied in shell because until finish is called, shell owns the surfaces.
1021      * Additionally, this gives shell the ability to better deal with merged transitions.
1022      */
buildFinishTransaction(SurfaceControl.Transaction t, TransitionInfo info)1023     private void buildFinishTransaction(SurfaceControl.Transaction t, TransitionInfo info) {
1024         // usually only size 1
1025         final ArraySet<DisplayContent> displays = new ArraySet<>();
1026         for (int i = mTargets.size() - 1; i >= 0; --i) {
1027             final WindowContainer<?> target = mTargets.get(i).mContainer;
1028             if (target.getParent() == null) continue;
1029             final SurfaceControl targetLeash = getLeashSurface(target, null /* t */);
1030             final SurfaceControl origParent = getOrigParentSurface(target);
1031             // Ensure surfaceControls are re-parented back into the hierarchy.
1032             t.reparent(targetLeash, origParent);
1033             t.setLayer(targetLeash, target.getLastLayer());
1034             t.setCornerRadius(targetLeash, 0);
1035             t.setShadowRadius(targetLeash, 0);
1036             t.setAlpha(targetLeash, 1);
1037             displays.add(target.getDisplayContent());
1038             // For config-at-end, the end-transform will be reset after the config is actually
1039             // applied in the client (since the transform depends on config). The other properties
1040             // remain here because shell might want to persistently override them.
1041             if ((mTargets.get(i).mFlags & ChangeInfo.FLAG_CHANGE_CONFIG_AT_END) == 0) {
1042                 resetSurfaceTransform(t, target, targetLeash);
1043             }
1044         }
1045         // Remove screenshot layers if necessary
1046         if (mContainerFreezer != null) {
1047             mContainerFreezer.cleanUp(t);
1048         }
1049         // Need to update layers on involved displays since they were all paused while
1050         // the animation played. This puts the layers back into the correct order.
1051         for (int i = displays.size() - 1; i >= 0; --i) {
1052             if (displays.valueAt(i) == null) continue;
1053             assignLayers(displays.valueAt(i), t);
1054         }
1055 
1056         for (int i = 0; i < info.getRootCount(); ++i) {
1057             t.reparent(info.getRoot(i).getLeash(), null);
1058         }
1059     }
1060 
1061     /** Assigns the layers for the start or end state of transition. */
assignLayers(WindowContainer<?> wc, SurfaceControl.Transaction t)1062     static void assignLayers(WindowContainer<?> wc, SurfaceControl.Transaction t) {
1063         wc.mTransitionController.mBuildingFinishLayers = true;
1064         try {
1065             wc.assignChildLayers(t);
1066         } finally {
1067             wc.mTransitionController.mBuildingFinishLayers = false;
1068         }
1069     }
1070 
1071     /**
1072      * Build a transaction that cleans-up transition-only surfaces (transition root and snapshots).
1073      * This will ALWAYS be applied on transition finish just in-case
1074      */
buildCleanupTransaction(SurfaceControl.Transaction t, TransitionInfo info)1075     private static void buildCleanupTransaction(SurfaceControl.Transaction t, TransitionInfo info) {
1076         for (int i = info.getChanges().size() - 1; i >= 0; --i) {
1077             final TransitionInfo.Change c = info.getChanges().get(i);
1078             if (c.getSnapshot() != null) {
1079                 t.reparent(c.getSnapshot(), null);
1080             }
1081             // The fixed transform hint was set in DisplayContent#applyRotation(). Make sure to
1082             // clear the hint in case the start transaction is not applied.
1083             if (c.hasFlags(FLAG_IS_DISPLAY) && c.getStartRotation() != c.getEndRotation()
1084                     && c.getContainer() != null) {
1085                 t.unsetFixedTransformHint(WindowContainer.fromBinder(c.getContainer().asBinder())
1086                         .asDisplayContent().mSurfaceControl);
1087             }
1088         }
1089         for (int i = info.getRootCount() - 1; i >= 0; --i) {
1090             final SurfaceControl leash = info.getRoot(i).getLeash();
1091             if (leash == null) continue;
1092             t.reparent(leash, null);
1093         }
1094     }
1095 
1096     /**
1097      * Set whether this transition can start a pip-enter transition when finished. This is usually
1098      * true, but gets set to false when recents decides that it wants to finish its animation but
1099      * not actually finish its animation (yeah...).
1100      */
setCanPipOnFinish(boolean canPipOnFinish)1101     void setCanPipOnFinish(boolean canPipOnFinish) {
1102         mCanPipOnFinish = canPipOnFinish;
1103     }
1104 
didCommitTransientLaunch()1105     private boolean didCommitTransientLaunch() {
1106         if (mTransientLaunches == null) return false;
1107         for (int j = 0; j < mTransientLaunches.size(); ++j) {
1108             if (mTransientLaunches.keyAt(j).isVisibleRequested()) {
1109                 return true;
1110             }
1111         }
1112         return false;
1113     }
1114 
1115     /**
1116      * Check if pip-entry is possible after finishing and enter-pip if it is.
1117      *
1118      * @return true if we are *guaranteed* to enter-pip. This means we return false if there's
1119      *         a chance we won't thus legacy-entry (via pause+userLeaving) will return false.
1120      */
checkEnterPipOnFinish(@onNull ActivityRecord ar)1121     private boolean checkEnterPipOnFinish(@NonNull ActivityRecord ar) {
1122         if (!mCanPipOnFinish || !ar.isVisible() || ar.getTask() == null || !ar.isState(RESUMED)) {
1123             return false;
1124         }
1125 
1126         final ActivityRecord resuming = getVisibleTransientLaunch(ar.getTaskDisplayArea());
1127         if (ar.pictureInPictureArgs != null && ar.pictureInPictureArgs.isAutoEnterEnabled()) {
1128             if (!ar.getTask().isVisibleRequested() || didCommitTransientLaunch()) {
1129                 // force enable pip-on-task-switch now that we've committed to actually launching
1130                 // to the transient activity.
1131                 ar.supportsEnterPipOnTaskSwitch = true;
1132             }
1133             // Make sure this activity can enter pip under the current circumstances.
1134             // `enterPictureInPicture` internally checks, but with beforeStopping=false which
1135             // is specifically for non-auto-enter.
1136             if (!ar.checkEnterPictureInPictureState("enterPictureInPictureMode",
1137                     true /* beforeStopping */)) {
1138                 return false;
1139             }
1140             final int prevMode = ar.getTask().getWindowingMode();
1141             final boolean inPip = mController.mAtm.enterPictureInPictureMode(ar,
1142                     ar.pictureInPictureArgs, false /* fromClient */, true /* isAutoEnter */);
1143             final int currentMode = ar.getTask().getWindowingMode();
1144             if (prevMode == WINDOWING_MODE_FULLSCREEN && currentMode == WINDOWING_MODE_PINNED
1145                     && mTransientLaunches != null
1146                     && ar.mDisplayContent.hasTopFixedRotationLaunchingApp()) {
1147                 // There will be a display configuration change after finishing this transition.
1148                 // Skip dispatching the change for PiP task to avoid its activity drawing for the
1149                 // intermediate state which will cause flickering. The final PiP bounds in new
1150                 // rotation will be applied by PipTransition.
1151                 ar.mDisplayContent.mPinnedTaskController.setEnterPipTransaction(null);
1152             }
1153             return inPip;
1154         }
1155 
1156         // Legacy pip-entry (not via isAutoEnterEnabled).
1157         if ((!ar.getTask().isVisibleRequested() || didCommitTransientLaunch())
1158                 && ar.supportsPictureInPicture()) {
1159             // force enable pip-on-task-switch now that we've committed to actually launching to the
1160             // transient activity, and then recalculate whether we can attempt pip.
1161             ar.supportsEnterPipOnTaskSwitch = true;
1162         }
1163 
1164         try {
1165             // If not going auto-pip, the activity should be paused with user-leaving.
1166             mController.mAtm.mTaskSupervisor.mUserLeaving = true;
1167             ar.getTaskFragment().startPausing(false /* uiSleeping */, resuming, "finishTransition");
1168         } finally {
1169             mController.mAtm.mTaskSupervisor.mUserLeaving = false;
1170         }
1171         // Return false anyway because there's no guarantee that the app will enter pip.
1172         return false;
1173     }
1174 
1175     /**
1176      * The transition has finished animating and is ready to finalize WM state. This should not
1177      * be called directly; use {@link TransitionController#finishTransition} instead.
1178      */
finishTransition()1179     void finishTransition() {
1180         if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER) && mIsPlayerEnabled) {
1181             asyncTraceEnd(System.identityHashCode(this));
1182         }
1183         mLogger.mFinishTimeNs = SystemClock.elapsedRealtimeNanos();
1184         mController.mLoggerHandler.post(mLogger::logOnFinish);
1185         mController.mTransitionTracer.logFinishedTransition(this);
1186         // Close the transactions now. They were originally copied to Shell in case we needed to
1187         // apply them due to a remote failure. Since we don't need to apply them anymore, free them
1188         // immediately.
1189         if (mStartTransaction != null) mStartTransaction.close();
1190         if (mFinishTransaction != null) mFinishTransaction.close();
1191         mStartTransaction = mFinishTransaction = null;
1192         if (mCleanupTransaction != null) {
1193             mCleanupTransaction.apply();
1194             mCleanupTransaction = null;
1195         }
1196         if (mState < STATE_PLAYING) {
1197             throw new IllegalStateException("Can't finish a non-playing transition " + mSyncId);
1198         }
1199         mController.mFinishingTransition = this;
1200         if (mTransientHideTasks != null && !mTransientHideTasks.isEmpty()) {
1201             // The transient hide tasks could be occluded now, e.g. returning to home. So trigger
1202             // the update to make the activities in the tasks invisible-requested, then the next
1203             // step can continue to commit the visibility.
1204             mController.mAtm.mRootWindowContainer.ensureActivitiesVisible();
1205             // Record all the now-hiding activities so that they are committed. Just use
1206             // mParticipants because we can avoid a new list this way.
1207             for (int i = 0; i < mTransientHideTasks.size(); ++i) {
1208                 final Task rootTask = mTransientHideTasks.get(i);
1209                 rootTask.forAllActivities(r -> {
1210                     // Only check leaf-tasks that were collected
1211                     if (!mParticipants.contains(r.getTask())) return;
1212                     if (rootTask.isVisibleRequested()) {
1213                         // This transient-hide didn't hide, so don't commit anything (otherwise we
1214                         // could prematurely commit invisible on unrelated activities). To be safe,
1215                         // though, notify the controller to prevent degenerate cases.
1216                         if (!r.isVisibleRequested()) {
1217                             mController.mValidateCommitVis.add(r);
1218                         } else {
1219                             // Make sure onAppTransitionFinished can be notified.
1220                             mParticipants.add(r);
1221                         }
1222                         return;
1223                     }
1224                     // This did hide: commit immediately so that other transitions know about it.
1225                     mParticipants.add(r);
1226                 });
1227             }
1228         }
1229 
1230         boolean hasParticipatedDisplay = false;
1231         boolean hasVisibleTransientLaunch = false;
1232         boolean enterAutoPip = false;
1233         boolean committedSomeInvisible = false;
1234         // Commit all going-invisible containers
1235         for (int i = 0; i < mParticipants.size(); ++i) {
1236             final WindowContainer<?> participant = mParticipants.valueAt(i);
1237             final ActivityRecord ar = participant.asActivityRecord();
1238             if (ar != null) {
1239                 final Task task = ar.getTask();
1240                 if (task == null) continue;
1241                 boolean visibleAtTransitionEnd = mVisibleAtTransitionEndTokens.contains(ar);
1242                 // visibleAtTransitionEnd is used to guard against pre-maturely committing
1243                 // invisible on a window which is actually hidden by a later transition and not this
1244                 // one. However, for a transient launch, we can't use this mechanism because the
1245                 // visibility is determined at finish. Instead, use a different heuristic: don't
1246                 // commit invisible if the window is already in a later transition. That later
1247                 // transition will then handle the commit.
1248                 if (isTransientLaunch(ar) && !ar.isVisibleRequested()
1249                         && mController.inCollectingTransition(ar)) {
1250                     visibleAtTransitionEnd = true;
1251                 }
1252                 // We need both the expected visibility AND current requested-visibility to be
1253                 // false. If it is expected-visible but not currently visible, it means that
1254                 // another animation is queued-up to animate this to invisibility, so we can't
1255                 // remove the surfaces yet. If it is currently visible, but not expected-visible,
1256                 // then doing commitVisibility here would actually be out-of-order and leave the
1257                 // activity in a bad state.
1258                 // TODO (b/243755838) Create a screen off transition to correct the visible status
1259                 // of activities.
1260                 final boolean isScreenOff = ar.mDisplayContent == null
1261                         || ar.mDisplayContent.getDisplayInfo().state == Display.STATE_OFF;
1262                 if ((!visibleAtTransitionEnd || isScreenOff) && !ar.isVisibleRequested()) {
1263                     final boolean commitVisibility = !checkEnterPipOnFinish(ar);
1264                     // Avoid commit visibility if entering pip or else we will get a sudden
1265                     // "flash" / surface going invisible for a split second.
1266                     if (commitVisibility) {
1267                         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
1268                                 "  Commit activity becoming invisible: %s", ar);
1269                         final SnapshotController snapController = mController.mSnapshotController;
1270                         if (mTransientLaunches != null && !task.isVisibleRequested()
1271                                 && !task.isActivityTypeHome()) {
1272                             final long startTimeNs = mLogger.mSendTimeNs;
1273                             final long lastSnapshotTimeNs = snapController.mTaskSnapshotController
1274                                     .getSnapshotCaptureTime(task.mTaskId);
1275                             // If transition is transient, then snapshots are taken at end of
1276                             // transition only if a snapshot was not already captured by request
1277                             // during the transition
1278                             if (lastSnapshotTimeNs < startTimeNs) {
1279                                 snapController.mTaskSnapshotController.recordSnapshot(task);
1280                             } else {
1281                                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
1282                                         "  Skipping post-transition snapshot for task %d",
1283                                         task.mTaskId);
1284                             }
1285                         }
1286                         ar.commitVisibility(false /* visible */, false /* performLayout */,
1287                                 true /* fromTransition */);
1288                         committedSomeInvisible = true;
1289                     } else {
1290                         enterAutoPip = true;
1291                     }
1292                 }
1293                 final ChangeInfo changeInfo = mChanges.get(ar);
1294                 // Due to transient-hide, there may be some activities here which weren't in the
1295                 // transition.
1296                 if (changeInfo != null && changeInfo.mVisible != visibleAtTransitionEnd) {
1297                     // Legacy dispatch relies on this (for now).
1298                     ar.mEnteringAnimation = visibleAtTransitionEnd;
1299                 } else if (mTransientLaunches != null && mTransientLaunches.containsKey(ar)
1300                         && ar.isVisible()) {
1301                     // Transient launch was committed, so report enteringAnimation
1302                     ar.mEnteringAnimation = true;
1303                     hasVisibleTransientLaunch = true;
1304 
1305                     // Since transient launches don't automatically take focus, make sure we
1306                     // synchronize focus since we committed to the launch.
1307                     if (!task.isFocused() && ar.isTopRunningActivity()) {
1308                         mController.mAtm.setLastResumedActivityUncheckLocked(ar,
1309                                 "transitionFinished");
1310                     }
1311                 }
1312                 continue;
1313             }
1314             if (participant.asDisplayContent() != null) {
1315                 hasParticipatedDisplay = true;
1316                 continue;
1317             }
1318             final Task tr = participant.asTask();
1319             if (tr != null && tr.isVisibleRequested() && tr.inPinnedWindowingMode()) {
1320                 final ActivityRecord top = tr.getTopNonFinishingActivity();
1321                 if (top != null && !top.inPinnedWindowingMode()) {
1322                     mController.mStateValidators.add(() -> {
1323                         if (!tr.isAttached() || !tr.isVisibleRequested()
1324                                 || !tr.inPinnedWindowingMode()) return;
1325                         final ActivityRecord currTop = tr.getTopNonFinishingActivity();
1326                         if (currTop.inPinnedWindowingMode()) return;
1327                         Slog.e(TAG, "Enter-PIP was started but not completed, this is a Shell/SysUI"
1328                                 + " bug. This state breaks gesture-nav, so attempting clean-up.");
1329                         // We don't know the destination bounds, so we can't actually finish the
1330                         // operation. So, to prevent the half-pipped task from covering everything,
1331                         // abort the action (which moves the task to back).
1332                         tr.abortPipEnter(currTop);
1333                     });
1334                 }
1335             }
1336         }
1337         // Commit wallpaper visibility after activity, because usually the wallpaper target token is
1338         // an activity, and wallpaper's visibility depends on activity's visibility.
1339         for (int i = mParticipants.size() - 1; i >= 0; --i) {
1340             final WallpaperWindowToken wt = mParticipants.valueAt(i).asWallpaperToken();
1341             if (wt == null) continue;
1342             final WindowState target = wt.mDisplayContent.mWallpaperController.getWallpaperTarget();
1343             final boolean isTargetInvisible = target == null || !target.mToken.isVisible();
1344             final boolean isWallpaperVisibleAtEnd =
1345                     wt.isVisibleRequested() || mVisibleAtTransitionEndTokens.contains(wt);
1346             if (isTargetInvisible || !isWallpaperVisibleAtEnd) {
1347                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
1348                         "  Commit wallpaper becoming invisible: %s", wt);
1349                 wt.commitVisibility(false /* visible */);
1350             }
1351             if (isTargetInvisible) {
1352                 // Our original target went invisible, so we should look for a new target.
1353                 wt.mDisplayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
1354             }
1355         }
1356         if (committedSomeInvisible) {
1357             mController.onCommittedInvisibles();
1358         }
1359 
1360         if (hasVisibleTransientLaunch) {
1361             // Notify the change about the transient-below task if entering auto-pip.
1362             if (enterAutoPip) {
1363                 mController.mAtm.getTaskChangeNotificationController().notifyTaskStackChanged();
1364             }
1365             // Prevent spurious background app switches.
1366             mController.mAtm.stopAppSwitches();
1367             // The end of transient launch may not reorder task, so make sure to compute the latest
1368             // task rank according to the current visibility.
1369             mController.mAtm.mRootWindowContainer.rankTaskLayers();
1370         }
1371 
1372         commitConfigAtEndActivities();
1373 
1374         // dispatch legacy callback in a different loop. This is because multiple legacy handlers
1375         // (fixed-rotation/displaycontent) make global changes, so we want to ensure that we've
1376         // processed all the participants first (in particular, we want to trigger pip-enter first)
1377         for (int i = 0; i < mParticipants.size(); ++i) {
1378             final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord();
1379             if (ar == null) continue;
1380 
1381             // If the activity was just inserted to an invisible task, it will keep INITIALIZING
1382             // state. Then no need to notify the callback to avoid clearing some states
1383             // unexpectedly, e.g. launch-task-behind.
1384             // However, skip dispatch to predictive back animation target, because it only set
1385             // launch-task-behind to make the activity become visible.
1386             if ((ar.isVisibleRequested() || !ar.isState(ActivityRecord.State.INITIALIZING))
1387                     && !ar.isAnimating(PARENTS, ANIMATION_TYPE_PREDICT_BACK)) {
1388                 mController.dispatchLegacyAppTransitionFinished(ar);
1389             }
1390 
1391             // Reset the ActivityRecord#mCurrentLaunchCanTurnScreenOn state if it is not the top
1392             // running activity. Doing so in case the state is not yet consumed during rapid
1393             // activity launch.
1394             if (ar.currentLaunchCanTurnScreenOn() && ar.getDisplayContent() != null
1395                     && ar.getDisplayContent().topRunningActivity() != ar) {
1396                 ar.setCurrentLaunchCanTurnScreenOn(false);
1397             }
1398         }
1399 
1400         // Update the input-sink (touch-blocking) state now that the animation is finished.
1401         SurfaceControl.Transaction inputSinkTransaction = null;
1402         for (int i = 0; i < mParticipants.size(); ++i) {
1403             final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord();
1404             if (ar == null || !ar.isVisible() || ar.getParent() == null) continue;
1405             if (inputSinkTransaction == null) {
1406                 inputSinkTransaction = ar.mWmService.mTransactionFactory.get();
1407             }
1408             ar.mActivityRecordInputSink.applyChangesToSurfaceIfChanged(inputSinkTransaction);
1409         }
1410         if (inputSinkTransaction != null) inputSinkTransaction.apply();
1411 
1412         // Always schedule stop processing when transition finishes because activities don't
1413         // stop while they are in a transition thus their stop could still be pending.
1414         mController.mAtm.mTaskSupervisor
1415                 .scheduleProcessStoppingAndFinishingActivitiesIfNeeded();
1416 
1417         sendRemoteCallback(mClientAnimationFinishCallback);
1418 
1419         legacyRestoreNavigationBarFromApp();
1420 
1421         if (mRecentsDisplayId != INVALID_DISPLAY) {
1422             // Clean up input monitors (for recents)
1423             final DisplayContent dc =
1424                     mController.mAtm.mRootWindowContainer.getDisplayContent(mRecentsDisplayId);
1425             dc.getInputMonitor().setActiveRecents(null /* activity */, null /* layer */);
1426             dc.getInputMonitor().updateInputWindowsLw(false /* force */);
1427         }
1428         if (mTransientLaunches != null) {
1429             for (int i = mTransientLaunches.size() - 1; i >= 0; --i) {
1430                 // Reset the ability of controlling SystemUi which might be changed by
1431                 // setTransientLaunch or setRecentsAppBehindSystemBars.
1432                 final Task task = mTransientLaunches.keyAt(i).getTask();
1433                 if (task != null) {
1434                     task.setCanAffectSystemUiFlags(true);
1435                 }
1436             }
1437         }
1438 
1439         for (int i = 0; i < mTargetDisplays.size(); ++i) {
1440             final DisplayContent dc = mTargetDisplays.get(i);
1441             final AsyncRotationController asyncRotationController = dc.getAsyncRotationController();
1442             if (asyncRotationController != null && containsChangeFor(dc, mTargets)) {
1443                 asyncRotationController.onTransitionFinished();
1444             }
1445             dc.onTransitionFinished();
1446             if (hasParticipatedDisplay && dc.mDisplayRotationCompatPolicy != null) {
1447                 final ChangeInfo changeInfo = mChanges.get(dc);
1448                 if (changeInfo != null
1449                         && changeInfo.mRotation != dc.getWindowConfiguration().getRotation()) {
1450                     dc.mDisplayRotationCompatPolicy.onScreenRotationAnimationFinished();
1451                 }
1452             }
1453             if (mTransientLaunches != null) {
1454                 TaskDisplayArea transientTDA = null;
1455                 for (int t = 0; t < mTransientLaunches.size(); ++t) {
1456                     if (mTransientLaunches.keyAt(t).getDisplayContent() == dc) {
1457                         if (hasVisibleTransientLaunch) {
1458                             updateImeForVisibleTransientLaunch(dc);
1459                         }
1460                         transientTDA = mTransientLaunches.keyAt(i).getTaskDisplayArea();
1461                         break;
1462                     }
1463                 }
1464                 if (!hasVisibleTransientLaunch && mRecentsDisplayId == dc.mDisplayId) {
1465                     // Restore IME icon only when moving the original app task to front from
1466                     // recents, in case IME icon may missing if the moving task has already been
1467                     // the current focused task.
1468                     InputMethodManagerInternal.get().updateImeWindowStatus(
1469                             false /* disableImeIcon */, dc.getDisplayId());
1470                 }
1471                 // An uncommitted transient launch can leave incomplete lifecycles if visibilities
1472                 // didn't change (eg. re-ordering with translucent tasks will leave launcher
1473                 // in RESUMED state), so force an update here.
1474                 if (!hasVisibleTransientLaunch && transientTDA != null) {
1475                     transientTDA.pauseBackTasks(null /* resuming */);
1476                 }
1477             }
1478             dc.removeImeSurfaceImmediately();
1479             dc.handleCompleteDeferredRemoval();
1480         }
1481         validateKeyguardOcclusion();
1482 
1483         mState = STATE_FINISHED;
1484         // Rotation change may be deferred while there is a display change transition, so check
1485         // again in case there is a new pending change.
1486         if (hasParticipatedDisplay && !mController.useShellTransitionsRotation()) {
1487             mController.mAtm.mWindowManager.updateRotation(false /* alwaysSendConfiguration */,
1488                     false /* forceRelayout */);
1489         }
1490         cleanUpInternal();
1491 
1492         // Handle back animation if it's already started.
1493         mController.mAtm.mBackNavigationController.onTransitionFinish(mTargets, this);
1494         mController.mFinishingTransition = null;
1495         mController.mSnapshotController.onTransitionFinish(mType, mTargets);
1496         // Resume snapshot persist thread after snapshot controller analysis this transition.
1497         mController.updateAnimatingState();
1498 
1499         invokeTransitionEndedListeners();
1500     }
1501 
invokeTransitionEndedListeners()1502     private void invokeTransitionEndedListeners() {
1503         if (mTransitionEndedListeners == null) {
1504             return;
1505         }
1506         for (int i = 0; i < mTransitionEndedListeners.size(); i++) {
1507             mTransitionEndedListeners.get(i).run();
1508         }
1509         mTransitionEndedListeners = null;
1510     }
1511 
commitConfigAtEndActivities()1512     private void commitConfigAtEndActivities() {
1513         if (mConfigAtEndActivities == null || mConfigAtEndActivities.isEmpty()) {
1514             return;
1515         }
1516         final SurfaceControl.Transaction t =
1517                 mController.mAtm.mWindowManager.mTransactionFactory.get();
1518         for (int i = 0; i < mTargets.size(); ++i) {
1519             final WindowContainer target = mTargets.get(i).mContainer;
1520             if (target.getParent() == null || (mTargets.get(i).mFlags
1521                     & ChangeInfo.FLAG_CHANGE_CONFIG_AT_END) == 0) {
1522                 continue;
1523             }
1524             final SurfaceControl targetLeash = getLeashSurface(target, null /* t */);
1525             // Reset surface state here (since it was skipped in buildFinishTransaction). Since
1526             // we are resuming config to the "current" state, we have to calculate the matching
1527             // surface state now (rather than snapshotting it at animation start).
1528             resetSurfaceTransform(t, target, targetLeash);
1529         }
1530 
1531         // Now we resume the configuration dispatch, wait until the now resumed configs have been
1532         // drawn, and then apply everything together.
1533         final BLASTSyncEngine.SyncGroup sg = mSyncEngine.prepareSyncSet(
1534                 new BLASTSyncEngine.TransactionReadyListener() {
1535                     @Override
1536                     public void onTransactionReady(int mSyncId,
1537                             SurfaceControl.Transaction transaction) {
1538                         t.merge(transaction);
1539                         t.apply();
1540                     }
1541 
1542                     @Override
1543                     public void onTransactionCommitTimeout() {
1544                         t.apply();
1545                     }
1546                 }, "ConfigAtTransitEnd");
1547         final int syncId = sg.mSyncId;
1548         mSyncEngine.startSyncSet(sg, BLAST_TIMEOUT_DURATION, true /* parallel */);
1549         mSyncEngine.setSyncMethod(syncId, BLASTSyncEngine.METHOD_BLAST);
1550         for (int i = 0; i < mConfigAtEndActivities.size(); ++i) {
1551             final ActivityRecord ar = mConfigAtEndActivities.get(i);
1552             mSyncEngine.addToSyncSet(syncId, ar);
1553             ar.resumeConfigurationDispatch();
1554         }
1555         mSyncEngine.setReady(syncId);
1556     }
1557 
1558     @Nullable
getVisibleTransientLaunch(TaskDisplayArea taskDisplayArea)1559     private ActivityRecord getVisibleTransientLaunch(TaskDisplayArea taskDisplayArea) {
1560         if (mTransientLaunches == null) return null;
1561         for (int i = mTransientLaunches.size() - 1; i >= 0; --i) {
1562             final ActivityRecord candidateActivity = mTransientLaunches.keyAt(i);
1563             if (candidateActivity.getTaskDisplayArea() != taskDisplayArea) {
1564                 continue;
1565             }
1566             if (!candidateActivity.isVisibleRequested()) {
1567                 continue;
1568             }
1569             return candidateActivity;
1570         }
1571         return null;
1572     }
1573 
1574     /**
1575      * Transient-launch activities cannot be IME target (see {@link WindowState#canBeImeTarget}),
1576      * so re-compute in case the IME target is changed after transition.
1577      */
updateImeForVisibleTransientLaunch(@onNull DisplayContent dc)1578     private void updateImeForVisibleTransientLaunch(@NonNull DisplayContent dc) {
1579         final WindowState imeTarget = dc.computeImeTarget(true /* updateImeTarget */);
1580         final WindowState imeWindow = dc.mInputMethodWindow;
1581         if (imeWindow == null || imeTarget == null
1582                 || !mController.hasCollectingRotationChange(dc, dc.getRotation())) {
1583             return;
1584         }
1585         // Drop the insets leash if it is still controlled by previous (invisible) app. This avoids
1586         // showing IME with old rotation on an app with new rotation if IME parent is updated
1587         // but insets leash hasn't been refreshed, i.e. DisplayContent#updateImeParent is called
1588         // but InsetsStateController#notifyControlTargetChanged still waits for IME to redraw.
1589         final InsetsSourceProvider sourceProvider = imeWindow.getControllableInsetProvider();
1590         if (sourceProvider == null || sourceProvider.mControl == null
1591                 || !sourceProvider.isClientVisible()
1592                 || imeTarget == sourceProvider.getControlTarget()) {
1593             return;
1594         }
1595         final SurfaceControl imeInsetsLeash = sourceProvider.mControl.getLeash();
1596         final InsetsControlTarget controlTarget = sourceProvider.getControlTarget();
1597         if (imeInsetsLeash != null && controlTarget != null && controlTarget.getWindow() != null
1598                 && !controlTarget.getWindow().mToken.isVisible()) {
1599             dc.getSyncTransaction().reparent(imeInsetsLeash, null);
1600         }
1601     }
1602 
abort()1603     void abort() {
1604         // This calls back into itself via controller.abort, so just early return here.
1605         if (mState == STATE_ABORT) return;
1606         if (mState == STATE_PENDING) {
1607             // hasn't started collecting, so can jump directly to aborted state.
1608             mState = STATE_ABORT;
1609             return;
1610         }
1611         if (mState != STATE_COLLECTING && mState != STATE_STARTED) {
1612             throw new IllegalStateException("Too late to abort. state=" + mState);
1613         }
1614         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Aborting Transition: %d", mSyncId);
1615         mState = STATE_ABORT;
1616         mLogger.mAbortTimeNs = SystemClock.elapsedRealtimeNanos();
1617         mController.mTransitionTracer.logAbortedTransition(this);
1618         // Syncengine abort will call through to onTransactionReady()
1619         mSyncEngine.abort(mSyncId);
1620         mController.dispatchLegacyAppTransitionCancelled();
1621         invokeTransitionEndedListeners();
1622     }
1623 
1624     /** Immediately moves this to playing even if it isn't started yet. */
playNow()1625     void playNow() {
1626         if (!(mState == STATE_COLLECTING || mState == STATE_STARTED)) {
1627             return;
1628         }
1629         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Force Playing Transition: %d",
1630                 mSyncId);
1631         mForcePlaying = true;
1632         // backwards since conditions are removed.
1633         for (int i = mReadyTracker.mConditions.size() - 1; i >= 0; --i) {
1634             mReadyTracker.mConditions.get(i).meetAlternate("play-now");
1635         }
1636         final ReadyCondition forcePlay = new ReadyCondition("force-play-now");
1637         mReadyTracker.add(forcePlay);
1638         forcePlay.meet();
1639         setAllReady();
1640         if (mState == STATE_COLLECTING) {
1641             start();
1642         }
1643         // Don't wait for actual surface-placement. We don't want anything else collected in this
1644         // transition.
1645         mSyncEngine.onSurfacePlacement();
1646     }
1647 
isForcePlaying()1648     boolean isForcePlaying() {
1649         return mForcePlaying;
1650     }
1651 
1652     /** Adjusts the priority of the process which will run the transition animation. */
setRemoteAnimationApp(IApplicationThread app)1653     void setRemoteAnimationApp(IApplicationThread app) {
1654         final WindowProcessController wpc = mController.mAtm.getProcessController(app);
1655         if (wpc != null) {
1656             // This is an early prediction. If the process doesn't ack the animation in 200 ms,
1657             // the priority will be restored.
1658             mController.mRemotePlayer.update(wpc, true /* running */, true /* predict */);
1659         }
1660     }
1661 
setNoAnimation(WindowContainer wc)1662     void setNoAnimation(WindowContainer wc) {
1663         final ChangeInfo change = mChanges.get(wc);
1664         if (change == null) {
1665             throw new IllegalStateException("Can't set no-animation property of non-participant");
1666         }
1667         change.mFlags |= ChangeInfo.FLAG_CHANGE_NO_ANIMATION;
1668     }
1669 
1670     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
containsChangeFor(WindowContainer wc, ArrayList<ChangeInfo> list)1671     static boolean containsChangeFor(WindowContainer wc, ArrayList<ChangeInfo> list) {
1672         for (int i = list.size() - 1; i >= 0; --i) {
1673             if (list.get(i).mContainer == wc) return true;
1674         }
1675         return false;
1676     }
1677 
1678     @Override
onTransactionReady(int syncId, SurfaceControl.Transaction transaction)1679     public void onTransactionReady(int syncId, SurfaceControl.Transaction transaction) {
1680         if (syncId != mSyncId) {
1681             Slog.e(TAG, "Unexpected Sync ID " + syncId + ". Expected " + mSyncId);
1682             return;
1683         }
1684 
1685         if (mController.useFullReadyTracking()) {
1686             for (int i = 0; i < mReadyTracker.mMet.size(); ++i) {
1687                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "#%d: Met condition: %s",
1688                         mSyncId, mReadyTracker.mMet.get(i));
1689             }
1690         }
1691 
1692         // Commit the visibility of visible activities before calculateTransitionInfo(), so the
1693         // TaskInfo can be visible. Also it needs to be done before moveToPlaying(), otherwise
1694         // ActivityRecord#canShowWindows() may reject to show its window. The visibility also
1695         // needs to be updated for STATE_ABORT.
1696         commitVisibleActivities(transaction);
1697         commitVisibleWallpapers(transaction);
1698 
1699         if (mTransactionCompletedListeners != null) {
1700             for (int i = 0; i < mTransactionCompletedListeners.size(); i++) {
1701                 final Runnable listener = mTransactionCompletedListeners.get(i);
1702                 transaction.addTransactionCompletedListener(Runnable::run,
1703                         (stats) -> listener.run());
1704             }
1705         }
1706 
1707         // Fall-back to the default display if there isn't one participating.
1708         final DisplayContent primaryDisplay = !mTargetDisplays.isEmpty() ? mTargetDisplays.get(0)
1709                 : mController.mAtm.mRootWindowContainer.getDefaultDisplay();
1710 
1711         if (mState == STATE_ABORT) {
1712             mController.onAbort(this);
1713             if (mConfigAtEndActivities != null) {
1714                 for (int i = 0; i < mConfigAtEndActivities.size(); ++i) {
1715                     mConfigAtEndActivities.get(i).resumeConfigurationDispatch();
1716                 }
1717                 mConfigAtEndActivities = null;
1718             }
1719             primaryDisplay.getPendingTransaction().merge(transaction);
1720             mSyncId = -1;
1721             mOverrideOptions = null;
1722             cleanUpInternal();
1723             return;
1724         }
1725 
1726         if (mState != STATE_STARTED) {
1727             Slog.e(TAG, "Playing a Transition which hasn't started! #" + mSyncId + " This will "
1728                     + "likely cause an exception in Shell");
1729         }
1730 
1731         mState = STATE_PLAYING;
1732         mStartTransaction = transaction;
1733         mFinishTransaction = mController.mAtm.mWindowManager.mTransactionFactory.get();
1734 
1735         // Flags must be assigned before calculateTransitionInfo. Otherwise it won't take effect.
1736         if (primaryDisplay.isKeyguardLocked()) {
1737             mFlags |= TRANSIT_FLAG_KEYGUARD_LOCKED;
1738         }
1739 
1740         // This is the only (or last) transition that is collecting, so we need to report any
1741         // leftover order changes.
1742         collectOrderChanges(mController.mWaitingTransitions.isEmpty());
1743 
1744         if (mPriorVisibilityMightBeDirty) {
1745             updatePriorVisibility();
1746         }
1747         // Resolve the animating targets from the participants.
1748         mTargets = calculateTargets(mParticipants, mChanges);
1749 
1750         // Check whether the participants were animated from back navigation.
1751         mController.mAtm.mBackNavigationController.onTransactionReady(this, mTargets,
1752                 transaction);
1753         final TransitionInfo info = calculateTransitionInfo(mType, mFlags, mTargets, transaction);
1754         info.setDebugId(mSyncId);
1755         mController.assignTrack(this, info);
1756 
1757         mController.moveToPlaying(this);
1758 
1759         // Repopulate the displays based on the resolved targets.
1760         mTargetDisplays.clear();
1761         for (int i = 0; i < info.getRootCount(); ++i) {
1762             final DisplayContent dc = mController.mAtm.mRootWindowContainer.getDisplayContent(
1763                     info.getRoot(i).getDisplayId());
1764             mTargetDisplays.add(dc);
1765         }
1766 
1767         for (int i = 0; i < mTargets.size(); ++i) {
1768             final DisplayArea da = mTargets.get(i).mContainer.asDisplayArea();
1769             if (da == null) continue;
1770             if (da.isVisibleRequested()) {
1771                 mController.mValidateDisplayVis.remove(da);
1772             } else {
1773                 // In case something accidentally hides a displayarea and nothing shows it again.
1774                 mController.mValidateDisplayVis.add(da);
1775             }
1776         }
1777         overrideAnimationOptionsToInfoIfNecessary(info);
1778 
1779         // TODO(b/188669821): Move to animation impl in shell.
1780         for (int i = 0; i < mTargetDisplays.size(); ++i) {
1781             handleLegacyRecentsStartBehavior(mTargetDisplays.get(i), info);
1782             if (mRecentsDisplayId != INVALID_DISPLAY) break;
1783         }
1784 
1785         // The callback is only populated for custom activity-level client animations
1786         sendRemoteCallback(mClientAnimationStartCallback);
1787 
1788         // Manually show any activities that are visibleRequested. This is needed to properly
1789         // support simultaneous animation queueing/merging. Specifically, if transition A makes
1790         // an activity invisible, it's finishTransaction (which is applied *after* the animation)
1791         // will hide the activity surface. If transition B then makes the activity visible again,
1792         // the normal surfaceplacement logic won't add a show to this start transaction because
1793         // the activity visibility hasn't been committed yet. To deal with this, we have to manually
1794         // show here in the same way that we manually hide in finishTransaction.
1795         for (int i = mParticipants.size() - 1; i >= 0; --i) {
1796             final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord();
1797             if (ar == null || !ar.isVisibleRequested()) continue;
1798             transaction.show(ar.getSurfaceControl());
1799 
1800             // Also manually show any non-reported parents. This is necessary in a few cases
1801             // where a task is NOT organized but had its visibility changed within its direct
1802             // parent. An example of this is if an alternate home leaf-task HB is started atop the
1803             // normal home leaf-task HA: these are both in the Home root-task HR, so there will be a
1804             // transition containing HA and HB where HA surface is hidden. If a standard task SA is
1805             // launched on top, then HB finishes, no transition will happen since neither home is
1806             // visible. When SA finishes, the transition contains HR rather than HA. Since home
1807             // leaf-tasks are NOT organized, HA won't be in the transition and thus its surface
1808             // wouldn't be shown. Just show is safe here since all other properties will have
1809             // already been reset by the original hiding-transition's finishTransaction (we can't
1810             // show in the finishTransaction because by then the activity doesn't hide until
1811             // surface placement).
1812             for (WindowContainer p = ar.getParent(); p != null && !containsChangeFor(p, mTargets);
1813                     p = p.getParent()) {
1814                 if (p.getSurfaceControl() != null) {
1815                     transaction.show(p.getSurfaceControl());
1816                 }
1817             }
1818         }
1819 
1820         // Record windowtokens (activity/wallpaper) that are expected to be visible after the
1821         // transition animation. This will be used in finishTransition to prevent prematurely
1822         // committing visibility. Skip transient launches since those are only temporarily visible.
1823         if (mTransientLaunches == null) {
1824             for (int i = mParticipants.size() - 1; i >= 0; --i) {
1825                 final WindowContainer wc = mParticipants.valueAt(i);
1826                 if (wc.asWindowToken() == null || !wc.isVisibleRequested()) continue;
1827                 mVisibleAtTransitionEndTokens.add(wc.asWindowToken());
1828             }
1829         }
1830 
1831         // This is non-null only if display has changes. It handles the visible windows that don't
1832         // need to be participated in the transition.
1833         for (int i = 0; i < mTargetDisplays.size(); ++i) {
1834             final DisplayContent dc = mTargetDisplays.get(i);
1835             final AsyncRotationController controller = dc.getAsyncRotationController();
1836             if (controller != null && containsChangeFor(dc, mTargets)) {
1837                 controller.setupStartTransaction(transaction);
1838             }
1839         }
1840         buildFinishTransaction(mFinishTransaction, info);
1841         mCleanupTransaction = mController.mAtm.mWindowManager.mTransactionFactory.get();
1842         buildCleanupTransaction(mCleanupTransaction, info);
1843         if (mController.getTransitionPlayer() != null && mIsPlayerEnabled) {
1844             mController.dispatchLegacyAppTransitionStarting(info, mStatusBarTransitionDelay);
1845             try {
1846                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
1847                         "Calling onTransitionReady: %s", info);
1848                 mLogger.mSendTimeNs = SystemClock.elapsedRealtimeNanos();
1849                 mLogger.mInfo = info;
1850                 mController.getTransitionPlayer().onTransitionReady(
1851                         mToken, info, transaction, mFinishTransaction);
1852                 if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
1853                     asyncTraceBegin(TRACE_NAME_PLAY_TRANSITION, System.identityHashCode(this));
1854                 }
1855             } catch (RemoteException e) {
1856                 // If there's an exception when trying to send the mergedTransaction to the
1857                 // client, we should finish and apply it here so the transactions aren't lost.
1858                 postCleanupOnFailure();
1859             }
1860             for (int i = 0; i < mTargetDisplays.size(); ++i) {
1861                 final DisplayContent dc = mTargetDisplays.get(i);
1862                 final AccessibilityController accessibilityController =
1863                         dc.mWmService.mAccessibilityController;
1864                 if (accessibilityController.hasCallbacks()) {
1865                     accessibilityController.onWMTransition(dc.getDisplayId(), mType, mFlags);
1866                 }
1867             }
1868         } else {
1869             // No player registered or it's not enabled, so just finish/apply immediately
1870             if (!mIsPlayerEnabled) {
1871                 mLogger.mSendTimeNs = SystemClock.uptimeNanos();
1872                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Apply and finish immediately"
1873                         + " because player is disabled for transition #%d .", mSyncId);
1874             }
1875             postCleanupOnFailure();
1876         }
1877         mOverrideOptions = null;
1878 
1879         reportStartReasonsToLogger();
1880 
1881         // Take snapshots for closing tasks/activities before the animation finished but after
1882         // dispatching onTransitionReady, so IME (if there is) can be captured together and the
1883         // time spent on snapshot won't delay the start of animation. Note that if this transition
1884         // is transient (mTransientLaunches != null), the snapshot will be captured at the end of
1885         // the transition, because IME won't move be moved during the transition and the tasks are
1886         // still live.
1887         if (mTransientLaunches == null) {
1888             mController.mSnapshotController.onTransactionReady(mType, mTargets);
1889         }
1890 
1891         // Since we created root-leash but no longer reference it from core, release it now
1892         info.releaseAnimSurfaces();
1893 
1894         if (mLogger.mInfo != null) {
1895             mLogger.logOnSendAsync(mController.mLoggerHandler);
1896             mController.mTransitionTracer.logSentTransition(this, mTargets);
1897         }
1898     }
1899 
overrideAnimationOptionsToInfoIfNecessary(@onNull TransitionInfo info)1900     private void overrideAnimationOptionsToInfoIfNecessary(@NonNull TransitionInfo info) {
1901         if (mOverrideOptions == null) {
1902             return;
1903         }
1904 
1905         if (!Flags.moveAnimationOptionsToChange()) {
1906             info.setAnimationOptions(mOverrideOptions);
1907         } else {
1908             final List<TransitionInfo.Change> changes = info.getChanges();
1909             for (int i = changes.size() - 1; i >= 0; --i) {
1910                 if (mTargets.get(i).mContainer.asActivityRecord() != null) {
1911                     changes.get(i).setAnimationOptions(mOverrideOptions);
1912                     // TODO(b/295805497): Extract mBackgroundColor from AnimationOptions.
1913                     changes.get(i).setBackgroundColor(mOverrideOptions.getBackgroundColor());
1914                 }
1915             }
1916         }
1917         updateActivityTargetForCrossProfileAnimation(info);
1918     }
1919 
1920     /**
1921      * Updates activity open target if {@link #mOverrideOptions} is
1922      * {@link ANIM_OPEN_CROSS_PROFILE_APPS}.
1923      */
updateActivityTargetForCrossProfileAnimation(@onNull TransitionInfo info)1924     private void updateActivityTargetForCrossProfileAnimation(@NonNull TransitionInfo info) {
1925         if (mOverrideOptions.getType() != ANIM_OPEN_CROSS_PROFILE_APPS) {
1926             return;
1927         }
1928         for (int i = 0; i < mTargets.size(); ++i) {
1929             final ActivityRecord activity = mTargets.get(i).mContainer
1930                     .asActivityRecord();
1931             final TransitionInfo.Change change = info.getChanges().get(i);
1932             if (activity == null || change.getMode() != TRANSIT_OPEN) {
1933                 continue;
1934             }
1935 
1936             int flags = change.getFlags();
1937             flags |= activity.mUserId == activity.mWmService.mCurrentUserId
1938                     ? TransitionInfo.FLAG_CROSS_PROFILE_OWNER_THUMBNAIL
1939                     : TransitionInfo.FLAG_CROSS_PROFILE_WORK_THUMBNAIL;
1940             change.setFlags(flags);
1941             break;
1942         }
1943     }
1944 
1945     @Override
onTransactionCommitTimeout()1946     public void onTransactionCommitTimeout() {
1947         if (mCleanupTransaction == null) return;
1948         for (int i = mTargetDisplays.size() - 1; i >= 0; --i) {
1949             final DisplayContent dc = mTargetDisplays.get(i);
1950             final AsyncRotationController asyncRotationController = dc.getAsyncRotationController();
1951             if (asyncRotationController != null && containsChangeFor(dc, mTargets)) {
1952                 asyncRotationController.onTransactionCommitTimeout(mCleanupTransaction);
1953             }
1954         }
1955     }
1956 
1957     /**
1958      * Adds a listener that will be executed after the start transaction of this transition
1959      * is presented on the screen, the listener will be executed on a binder thread
1960      */
addTransactionCompletedListener(Runnable listener)1961     void addTransactionCompletedListener(Runnable listener) {
1962         if (mTransactionCompletedListeners == null) {
1963             mTransactionCompletedListeners = new ArrayList<>();
1964         }
1965         mTransactionCompletedListeners.add(listener);
1966     }
1967 
1968     /**
1969      * Adds a listener that will be executed after the transition is finished or aborted.
1970      */
addTransitionEndedListener(Runnable listener)1971     void addTransitionEndedListener(Runnable listener) {
1972         if (mState != STATE_COLLECTING && mState != STATE_STARTED) {
1973             throw new IllegalStateException(
1974                     "Can't register listeners if the transition isn't collecting. state=" + mState);
1975         }
1976         if (mTransitionEndedListeners == null) {
1977             mTransitionEndedListeners = new ArrayList<>();
1978         }
1979         mTransitionEndedListeners.add(listener);
1980     }
1981 
1982     /**
1983      * Checks if the transition contains order changes.
1984      *
1985      * This is a shallow check that doesn't account for collection in parallel, unlike
1986      * {@code collectOrderChanges}
1987      */
hasOrderChanges()1988     boolean hasOrderChanges() {
1989         ArrayList<Task> onTopTasks = new ArrayList<>();
1990         // Iterate over target displays to get up to date on top tasks.
1991         // Cannot use `mOnTopTasksAtReady` as it's not populated before the `applyReady` is called.
1992         for (DisplayContent dc : mTargetDisplays) {
1993             addOnTopTasks(dc, onTopTasks);
1994         }
1995         for (Task task : onTopTasks) {
1996             if (!mOnTopTasksStart.contains(task)) {
1997                 return true;
1998             }
1999         }
2000         return false;
2001     }
2002 
2003     /**
2004      * Collect tasks which moved-to-top as part of this transition. This also updates the
2005      * controller's latest-reported when relevant.
2006      *
2007      * This is a non-trivial operation because transition can collect in parallel; however, it can
2008      * be made tenable by acknowledging that the "setup" part of collection (phase 1) is still
2009      * globally serial; so, we can build some reasonable rules around it.
2010      *
2011      * First, we record the "start" on-top state (to compare against). Then, when this becomes
2012      * ready (via allReady, NOT onTransactionReady), we also record the "onReady" on-top state
2013      * -- the idea here is that upon "allReady", all the actual WM changes should be done and we
2014      * are now just waiting for window content to become ready (finish drawing).
2015      *
2016      * Then, in this function (during onTransactionReady), we compare the two orders and include
2017      * any changes to the order in the reported transition-info. Unfortunately, because of parallel
2018      * collection, the order can change in unexpected ways by now. To resolve this, we ALSO keep a
2019      * global "latest reported order" in TransitionController and use that to make decisions.
2020      */
2021     @VisibleForTesting
collectOrderChanges(boolean reportCurrent)2022     void collectOrderChanges(boolean reportCurrent) {
2023         if (mOnTopTasksStart.isEmpty()) return;
2024         boolean includesOrderChange = false;
2025         for (int i = 0; i < mOnTopTasksAtReady.size(); ++i) {
2026             final Task task = mOnTopTasksAtReady.get(i);
2027             if (mOnTopTasksStart.contains(task)) continue;
2028             includesOrderChange = true;
2029             break;
2030         }
2031         if (!includesOrderChange && !reportCurrent) {
2032             // This transition doesn't include an order change, so if it isn't required to report
2033             // the current focus (eg. it's the last of a cluster of transitions), then don't
2034             // report.
2035             return;
2036         }
2037         // The transition included an order change, but it may not be up-to-date, so grab the
2038         // latest state and compare with the last reported state (or our start state if no
2039         // reported state exists).
2040         ArrayList<Task> onTopTasksEnd = new ArrayList<>();
2041         for (int d = 0; d < mTargetDisplays.size(); ++d) {
2042             addOnTopTasks(mTargetDisplays.get(d), onTopTasksEnd);
2043             final int displayId = mTargetDisplays.get(d).mDisplayId;
2044             ArrayList<Task> reportedOnTop = mController.mLatestOnTopTasksReported.get(displayId);
2045             for (int i = onTopTasksEnd.size() - 1; i >= 0; --i) {
2046                 final Task task = onTopTasksEnd.get(i);
2047                 if (task.getDisplayId() != displayId) continue;
2048                 // If it didn't change since last report, don't report
2049                 if (reportedOnTop == null) {
2050                     if (mOnTopTasksStart.contains(task)) continue;
2051                 } else if (reportedOnTop.contains(task)) {
2052                     continue;
2053                 }
2054                 // Need to report it.
2055                 mParticipants.add(task);
2056                 int changeIdx = mChanges.indexOfKey(task);
2057                 if (changeIdx < 0) {
2058                     mChanges.put(task, new ChangeInfo(task));
2059                     changeIdx = mChanges.indexOfKey(task);
2060                 }
2061                 mChanges.valueAt(changeIdx).mFlags |= ChangeInfo.FLAG_CHANGE_MOVED_TO_TOP;
2062             }
2063             // Swap in the latest on-top tasks.
2064             mController.mLatestOnTopTasksReported.put(displayId, onTopTasksEnd);
2065             onTopTasksEnd = reportedOnTop != null ? reportedOnTop : new ArrayList<>();
2066             onTopTasksEnd.clear();
2067         }
2068     }
2069 
postCleanupOnFailure()2070     private void postCleanupOnFailure() {
2071         mController.mAtm.mH.post(() -> {
2072             synchronized (mController.mAtm.mGlobalLock) {
2073                 cleanUpOnFailure();
2074             }
2075         });
2076     }
2077 
2078     /**
2079      * If the remote failed for any reason, use this to do any appropriate clean-up. Do not call
2080      * this directly, it's designed to by called by {@link TransitionController} only.
2081      */
cleanUpOnFailure()2082     void cleanUpOnFailure() {
2083         // No need to clean-up if this isn't playing yet.
2084         if (mState < STATE_PLAYING) return;
2085 
2086         if (mStartTransaction != null) {
2087             mStartTransaction.apply();
2088         }
2089         if (mFinishTransaction != null) {
2090             mFinishTransaction.apply();
2091         }
2092         mController.finishTransition(this);
2093     }
2094 
cleanUpInternal()2095     private void cleanUpInternal() {
2096         // Clean-up any native references.
2097         for (int i = 0; i < mChanges.size(); ++i) {
2098             final ChangeInfo ci = mChanges.valueAt(i);
2099             if (ci.mSnapshot != null) {
2100                 ci.mSnapshot.release();
2101             }
2102         }
2103         if (mCleanupTransaction != null) {
2104             mCleanupTransaction.apply();
2105             mCleanupTransaction = null;
2106         }
2107     }
2108 
2109     /** The transition is ready to play. Make the start transaction show the surfaces. */
commitVisibleActivities(SurfaceControl.Transaction transaction)2110     private void commitVisibleActivities(SurfaceControl.Transaction transaction) {
2111         for (int i = mParticipants.size() - 1; i >= 0; --i) {
2112             final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord();
2113             if (ar == null || ar.getTask() == null) {
2114                 continue;
2115             }
2116             if (ar.isVisibleRequested()) {
2117                 ar.commitVisibility(true /* visible */, false /* performLayout */,
2118                         true /* fromTransition */);
2119                 ar.commitFinishDrawing(transaction);
2120             }
2121             ar.getTask().setDeferTaskAppear(false);
2122         }
2123     }
2124 
2125     /**
2126      * Reset waitingToshow for all wallpapers, and commit the visibility of the visible ones
2127      */
commitVisibleWallpapers(SurfaceControl.Transaction t)2128     private void commitVisibleWallpapers(SurfaceControl.Transaction t) {
2129         boolean showWallpaper = shouldWallpaperBeVisible();
2130         for (int i = mParticipants.size() - 1; i >= 0; --i) {
2131             final WallpaperWindowToken wallpaper = mParticipants.valueAt(i).asWallpaperToken();
2132             if (wallpaper != null) {
2133                 if (!wallpaper.isVisible() && wallpaper.isVisibleRequested()) {
2134                     wallpaper.commitVisibility(showWallpaper);
2135                 }
2136                 if (showWallpaper && Flags.ensureWallpaperInTransitions()
2137                         && wallpaper.isVisibleRequested()
2138                         && getLeashSurface(wallpaper, t) != wallpaper.getSurfaceControl()) {
2139                     // If on a rotation leash, we need to explicitly show the wallpaper surface
2140                     // because shell only gets the leash and we don't allow non-transition logic
2141                     // to touch the surfaces until the transition is over.
2142                     t.show(wallpaper.getSurfaceControl());
2143                 }
2144             }
2145         }
2146     }
2147 
shouldWallpaperBeVisible()2148     private boolean shouldWallpaperBeVisible() {
2149         for (int i = mParticipants.size() - 1; i >= 0; --i) {
2150             WindowContainer participant = mParticipants.valueAt(i);
2151             if (participant.showWallpaper()) return true;
2152         }
2153         return false;
2154     }
2155 
2156     // TODO(b/188595497): Remove after migrating to shell.
2157     /** @see RecentsAnimationController#attachNavigationBarToApp */
handleLegacyRecentsStartBehavior(DisplayContent dc, TransitionInfo info)2158     private void handleLegacyRecentsStartBehavior(DisplayContent dc, TransitionInfo info) {
2159         if ((mFlags & TRANSIT_FLAG_IS_RECENTS) == 0) {
2160             return;
2161         }
2162 
2163         // Recents has an input-consumer to grab input from the "live tile" app. Set that up here
2164         final InputConsumerImpl recentsAnimationInputConsumer =
2165                 dc.getInputMonitor().getInputConsumer(INPUT_CONSUMER_RECENTS_ANIMATION);
2166         ActivityRecord recentsActivity = null;
2167         if (recentsAnimationInputConsumer != null) {
2168             // Find the top-most going-away task and the recents activity. The top-most
2169             // is used as layer reference while the recents is used for registering the consumer
2170             // override.
2171             Task topNonRecentsTask = null;
2172             for (int i = 0; i < info.getChanges().size(); ++i) {
2173                 final ActivityManager.RunningTaskInfo taskInfo =
2174                         info.getChanges().get(i).getTaskInfo();
2175                 if (taskInfo == null) continue;
2176                 final Task task = Task.fromWindowContainerToken(taskInfo.token);
2177                 if (task == null) continue;
2178                 final int activityType = taskInfo.topActivityType;
2179                 final boolean isRecents = activityType == ACTIVITY_TYPE_HOME
2180                         || activityType == ACTIVITY_TYPE_RECENTS;
2181                 if (isRecents && recentsActivity == null) {
2182                     recentsActivity = task.getTopVisibleActivity();
2183                 } else if (!isRecents && topNonRecentsTask == null) {
2184                     topNonRecentsTask = task;
2185                 }
2186             }
2187             if (recentsActivity != null && topNonRecentsTask != null) {
2188                 recentsAnimationInputConsumer.mWindowHandle.touchableRegion.set(
2189                         topNonRecentsTask.getBounds());
2190                 dc.getInputMonitor().setActiveRecents(recentsActivity, topNonRecentsTask);
2191             }
2192         }
2193 
2194         if (recentsActivity == null) {
2195             // No recents activity on `dc`, its probably on a different display.
2196             return;
2197         }
2198         mRecentsDisplayId = dc.mDisplayId;
2199 
2200         // The rest of this function handles nav-bar reparenting
2201 
2202         if (!dc.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition()
2203                 // Skip the case where the nav bar is controlled by fade rotation.
2204                 || dc.getAsyncRotationController() != null) {
2205             return;
2206         }
2207 
2208         WindowContainer topWC = null;
2209         // Find the top-most non-home, closing app.
2210         for (int i = 0; i < info.getChanges().size(); ++i) {
2211             final TransitionInfo.Change c = info.getChanges().get(i);
2212             if (c.getTaskInfo() == null || c.getTaskInfo().displayId != mRecentsDisplayId
2213                     || c.getTaskInfo().getActivityType() != ACTIVITY_TYPE_STANDARD
2214                     || !(c.getMode() == TRANSIT_CLOSE || c.getMode() == TRANSIT_TO_BACK)) {
2215                 continue;
2216             }
2217             topWC = WindowContainer.fromBinder(c.getContainer().asBinder());
2218             break;
2219         }
2220         if (topWC == null || topWC.inMultiWindowMode()) {
2221             return;
2222         }
2223 
2224         final WindowState navWindow = dc.getDisplayPolicy().getNavigationBar();
2225         if (navWindow == null || navWindow.mToken == null) {
2226             return;
2227         }
2228         mController.mNavigationBarAttachedToApp = true;
2229         navWindow.mToken.cancelAnimation();
2230         final SurfaceControl.Transaction t = navWindow.mToken.getPendingTransaction();
2231         final SurfaceControl navSurfaceControl = navWindow.mToken.getSurfaceControl();
2232         t.reparent(navSurfaceControl, topWC.getSurfaceControl());
2233         t.show(navSurfaceControl);
2234 
2235         final WindowContainer imeContainer = dc.getImeContainer();
2236         if (imeContainer.isVisible()) {
2237             t.setRelativeLayer(navSurfaceControl, imeContainer.getSurfaceControl(), 1);
2238         } else {
2239             // Place the nav bar on top of anything else in the top activity.
2240             t.setLayer(navSurfaceControl, Integer.MAX_VALUE);
2241         }
2242         final StatusBarManagerInternal bar = dc.getDisplayPolicy().getStatusBarManagerInternal();
2243         if (bar != null) {
2244             bar.setNavigationBarLumaSamplingEnabled(mRecentsDisplayId, false);
2245         }
2246     }
2247 
2248     /** @see RecentsAnimationController#restoreNavigationBarFromApp */
legacyRestoreNavigationBarFromApp()2249     void legacyRestoreNavigationBarFromApp() {
2250         if (!mController.mNavigationBarAttachedToApp) {
2251             return;
2252         }
2253         mController.mNavigationBarAttachedToApp = false;
2254 
2255         int recentsDisplayId = mRecentsDisplayId;
2256         if (recentsDisplayId == INVALID_DISPLAY) {
2257             Slog.i(TAG, "Restore parent surface of navigation bar by another transition");
2258             recentsDisplayId = DEFAULT_DISPLAY;
2259         }
2260 
2261         final DisplayContent dc =
2262                 mController.mAtm.mRootWindowContainer.getDisplayContent(recentsDisplayId);
2263         final StatusBarManagerInternal bar = dc.getDisplayPolicy().getStatusBarManagerInternal();
2264         if (bar != null) {
2265             bar.setNavigationBarLumaSamplingEnabled(recentsDisplayId, true);
2266         }
2267         final WindowState navWindow = dc.getDisplayPolicy().getNavigationBar();
2268         if (navWindow == null) return;
2269         navWindow.setSurfaceTranslationY(0);
2270 
2271         final WindowToken navToken = navWindow.mToken;
2272         if (navToken == null) return;
2273         final SurfaceControl.Transaction t = dc.getPendingTransaction();
2274         final WindowContainer parent = navToken.getParent();
2275         t.setLayer(navToken.getSurfaceControl(), navToken.getLastLayer());
2276 
2277         boolean animate = false;
2278         // Search for the home task. If it is supposed to be visible, then the navbar is not at
2279         // the bottom of the screen, so we need to animate it.
2280         for (int i = 0; i < mTargets.size(); ++i) {
2281             final Task task = mTargets.get(i).mContainer.asTask();
2282             if (task == null || !task.isActivityTypeHomeOrRecents()) continue;
2283             animate = task.isVisibleRequested();
2284             break;
2285         }
2286 
2287         if (animate) {
2288             final NavBarFadeAnimationController controller =
2289                     new NavBarFadeAnimationController(dc);
2290             controller.fadeWindowToken(true);
2291         } else {
2292             // Reparent the SurfaceControl of nav bar token back.
2293             t.reparent(navToken.getSurfaceControl(), parent.getSurfaceControl());
2294         }
2295 
2296         // To apply transactions.
2297         dc.mWmService.scheduleAnimationLocked();
2298     }
2299 
reportStartReasonsToLogger()2300     private void reportStartReasonsToLogger() {
2301         // Record transition start in metrics logger. We just assume everything is "DRAWN"
2302         // at this point since splash-screen is a presentation (shell) detail.
2303         ArrayMap<WindowContainer, Integer> reasons = new ArrayMap<>();
2304         for (int i = mParticipants.size() - 1; i >= 0; --i) {
2305             ActivityRecord r = mParticipants.valueAt(i).asActivityRecord();
2306             if (r == null || !r.isVisibleRequested()) continue;
2307             int transitionReason = APP_TRANSITION_WINDOWS_DRAWN;
2308             // At this point, r is "ready", but if it's not "ALL ready" then it is probably only
2309             // ready due to starting-window.
2310             if (r.mStartingData instanceof SplashScreenStartingData && !r.mLastAllReadyAtSync) {
2311                 transitionReason = APP_TRANSITION_SPLASH_SCREEN;
2312             } else if (r.isActivityTypeHomeOrRecents() && isTransientLaunch(r)) {
2313                 transitionReason = APP_TRANSITION_RECENTS_ANIM;
2314             }
2315             reasons.put(r, transitionReason);
2316         }
2317         mController.mAtm.mTaskSupervisor.getActivityMetricsLogger().notifyTransitionStarting(
2318                 reasons);
2319     }
2320 
2321     @Override
toString()2322     public String toString() {
2323         StringBuilder sb = new StringBuilder(64);
2324         sb.append("TransitionRecord{");
2325         sb.append(Integer.toHexString(System.identityHashCode(this)));
2326         sb.append(" id=" + mSyncId);
2327         sb.append(" type=" + transitTypeToString(mType));
2328         sb.append(" flags=0x" + Integer.toHexString(mFlags));
2329         sb.append('}');
2330         return sb.toString();
2331     }
2332 
2333     /** Returns the parent that the remote animator can animate or control. */
getAnimatableParent(WindowContainer<?> wc)2334     private static WindowContainer<?> getAnimatableParent(WindowContainer<?> wc) {
2335         WindowContainer<?> parent = wc.getParent();
2336         while (parent != null
2337                 && (!parent.canCreateRemoteAnimationTarget() && !parent.isOrganized())) {
2338             parent = parent.getParent();
2339         }
2340         return parent;
2341     }
2342 
reportIfNotTop(WindowContainer wc)2343     private static boolean reportIfNotTop(WindowContainer wc) {
2344         // Organized tasks need to be reported anyways because Core won't show() their surfaces
2345         // and we can't rely on onTaskAppeared because it isn't in sync.
2346         // TODO(shell-transitions): switch onTaskAppeared usage over to transitions OPEN.
2347         return wc.isOrganized();
2348     }
2349 
isWallpaper(WindowContainer wc)2350     private static boolean isWallpaper(WindowContainer wc) {
2351         return wc.asWallpaperToken() != null;
2352     }
2353 
isInputMethod(WindowContainer wc)2354     private static boolean isInputMethod(WindowContainer wc) {
2355         return wc.getWindowType() == TYPE_INPUT_METHOD;
2356     }
2357 
occludesKeyguard(WindowContainer wc)2358     private static boolean occludesKeyguard(WindowContainer wc) {
2359         final ActivityRecord ar = wc.asActivityRecord();
2360         if (ar != null) {
2361             return ar.canShowWhenLocked();
2362         }
2363         final Task t = wc.asTask();
2364         if (t != null) {
2365             // Get the top activity which was visible (since this is going away, it will remain
2366             // client visible until the transition is finished).
2367             // skip hidden (or about to hide) apps
2368             final ActivityRecord top = t.getActivity(WindowToken::isClientVisible);
2369             return top != null && top.canShowWhenLocked();
2370         }
2371         return false;
2372     }
2373 
isTranslucent(@onNull WindowContainer wc)2374     private static boolean isTranslucent(@NonNull WindowContainer wc) {
2375         final TaskFragment taskFragment = wc.asTaskFragment();
2376         if (taskFragment == null) {
2377             return !wc.fillsParent();
2378         }
2379 
2380         // Check containers differently as they are affected by child visibility.
2381 
2382         if (taskFragment.isTranslucentForTransition()) {
2383             // TaskFragment doesn't contain occluded ActivityRecord.
2384             return true;
2385         }
2386         final TaskFragment adjacentTaskFragment = taskFragment.getAdjacentTaskFragment();
2387         if (adjacentTaskFragment != null) {
2388             // When the TaskFragment has an adjacent TaskFragment, sibling behind them should be
2389             // hidden unless any of them are translucent.
2390             return adjacentTaskFragment.isTranslucentForTransition();
2391         } else {
2392             // Non-filling without adjacent is considered as translucent.
2393             return !wc.fillsParent();
2394         }
2395     }
2396 
updatePriorVisibility()2397     private void updatePriorVisibility() {
2398         for (int i = 0; i < mChanges.size(); ++i) {
2399             final ChangeInfo chg = mChanges.valueAt(i);
2400             // For task/activity, recalculate the current "real" visibility.
2401             if (chg.mContainer.asActivityRecord() == null && chg.mContainer.asTask() == null) {
2402                 continue;
2403             }
2404             // This ONLY works in the visible -> invisible case (and is only needed for this case)
2405             // because commitVisible(false) is deferred until finish.
2406             if (!chg.mVisible) continue;
2407             chg.mVisible = chg.mContainer.isVisible();
2408         }
2409     }
2410 
2411     /**
2412      * Under some conditions (eg. all visible targets within a parent container are transitioning
2413      * the same way) the transition can be "promoted" to the parent container. This means an
2414      * animation can play just on the parent rather than all the individual children.
2415      *
2416      * @return {@code true} if transition in target can be promoted to its parent.
2417      */
canPromote(ChangeInfo targetChange, Targets targets, ArrayMap<WindowContainer, ChangeInfo> changes)2418     private static boolean canPromote(ChangeInfo targetChange, Targets targets,
2419             ArrayMap<WindowContainer, ChangeInfo> changes) {
2420         final WindowContainer<?> target = targetChange.mContainer;
2421         final WindowContainer<?> parent = target.getParent();
2422         final ChangeInfo parentChange = changes.get(parent);
2423         if (!parent.canCreateRemoteAnimationTarget()
2424                 || parentChange == null || !parentChange.hasChanged()) {
2425             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "      SKIP: %s",
2426                     "parent can't be target " + parent);
2427             return false;
2428         }
2429         if (isWallpaper(target)) {
2430             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "      SKIP: is wallpaper");
2431             return false;
2432         }
2433 
2434         if (targetChange.mStartParent != null && target.getParent() != targetChange.mStartParent) {
2435             // When a window is reparented, the state change won't fit into any of the parents.
2436             // Don't promote such change so that we can animate the reparent if needed.
2437             return false;
2438         }
2439 
2440         final @TransitionInfo.TransitionMode int mode = targetChange.getTransitMode(target);
2441         for (int i = parent.getChildCount() - 1; i >= 0; --i) {
2442             final WindowContainer<?> sibling = parent.getChildAt(i);
2443             if (target == sibling) continue;
2444             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "      check sibling %s",
2445                     sibling);
2446             final ChangeInfo siblingChange = changes.get(sibling);
2447             if (siblingChange == null || !targets.wasParticipated(siblingChange)) {
2448                 if (sibling.isVisibleRequested()) {
2449                     // Sibling is visible but not animating, so no promote.
2450                     ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
2451                             "        SKIP: sibling is visible but not part of transition");
2452                     return false;
2453                 }
2454                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
2455                         "        unrelated invisible sibling %s", sibling);
2456                 continue;
2457             }
2458 
2459             final int siblingMode = siblingChange.getTransitMode(sibling);
2460             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
2461                     "        sibling is a participant with mode %s",
2462                     TransitionInfo.modeToString(siblingMode));
2463             if (reduceMode(mode) != reduceMode(siblingMode)) {
2464                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
2465                         "          SKIP: common mode mismatch. was %s",
2466                         TransitionInfo.modeToString(mode));
2467                 return false;
2468             }
2469         }
2470         return true;
2471     }
2472 
2473     /** "reduces" a mode into a smaller set of modes that uniquely represents visibility change. */
2474     @TransitionInfo.TransitionMode
reduceMode(@ransitionInfo.TransitionMode int mode)2475     private static int reduceMode(@TransitionInfo.TransitionMode int mode) {
2476         switch (mode) {
2477             case TRANSIT_TO_BACK: return TRANSIT_CLOSE;
2478             case TRANSIT_TO_FRONT: return TRANSIT_OPEN;
2479             default: return mode;
2480         }
2481     }
2482 
2483     /**
2484      * Go through topTargets and try to promote (see {@link #canPromote}) one of them.
2485      *
2486      * @param targets all targets that will be sent to the player.
2487      */
tryPromote(Targets targets, ArrayMap<WindowContainer, ChangeInfo> changes)2488     private static void tryPromote(Targets targets, ArrayMap<WindowContainer, ChangeInfo> changes) {
2489         WindowContainer<?> lastNonPromotableParent = null;
2490         // Go through from the deepest target.
2491         for (int i = targets.mArray.size() - 1; i >= 0; --i) {
2492             final ChangeInfo targetChange = targets.mArray.valueAt(i);
2493             final WindowContainer<?> target = targetChange.mContainer;
2494             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "    checking %s", target);
2495             final WindowContainer<?> parent = target.getParent();
2496             if (parent == lastNonPromotableParent) {
2497                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
2498                         "      SKIP: its sibling was rejected");
2499                 continue;
2500             }
2501             if (!canPromote(targetChange, targets, changes)) {
2502                 lastNonPromotableParent = parent;
2503                 continue;
2504             }
2505             if (reportIfNotTop(target)) {
2506                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
2507                         "        keep as target %s", target);
2508             } else {
2509                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
2510                         "        remove from targets %s", target);
2511                 targets.remove(i);
2512             }
2513             final ChangeInfo parentChange = changes.get(parent);
2514             if (targets.mArray.indexOfValue(parentChange) < 0) {
2515                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
2516                         "      CAN PROMOTE: promoting to parent %s", parent);
2517                 // The parent has lower depth, so it will be checked in the later iteration.
2518                 i++;
2519                 targets.add(parentChange);
2520             }
2521             if ((targetChange.mFlags & ChangeInfo.FLAG_CHANGE_NO_ANIMATION) != 0) {
2522                 parentChange.mFlags |= ChangeInfo.FLAG_CHANGE_NO_ANIMATION;
2523             } else {
2524                 parentChange.mFlags |= ChangeInfo.FLAG_CHANGE_YES_ANIMATION;
2525             }
2526             if ((targetChange.mFlags & ChangeInfo.FLAG_CHANGE_CONFIG_AT_END) != 0) {
2527                 parentChange.mFlags |= ChangeInfo.FLAG_CHANGE_CONFIG_AT_END;
2528             }
2529         }
2530     }
2531 
2532     /**
2533      * Find WindowContainers to be animated from a set of opening and closing apps. We will promote
2534      * animation targets to higher level in the window hierarchy if possible.
2535      */
2536     @VisibleForTesting
2537     @NonNull
calculateTargets(ArraySet<WindowContainer> participants, ArrayMap<WindowContainer, ChangeInfo> changes)2538     static ArrayList<ChangeInfo> calculateTargets(ArraySet<WindowContainer> participants,
2539             ArrayMap<WindowContainer, ChangeInfo> changes) {
2540         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
2541                 "Start calculating TransitionInfo based on participants: %s", participants);
2542 
2543         // Add all valid participants to the target container.
2544         final Targets targets = new Targets();
2545         for (int i = participants.size() - 1; i >= 0; --i) {
2546             final WindowContainer<?> wc = participants.valueAt(i);
2547             if (!wc.isAttached()) {
2548                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
2549                         "  Rejecting as detached: %s", wc);
2550                 continue;
2551             }
2552             // The level of transition target should be at least window token.
2553             if (wc.asWindowState() != null) continue;
2554 
2555             final ChangeInfo changeInfo = changes.get(wc);
2556             // Reject no-ops, unless wallpaper
2557             if (!changeInfo.hasChanged()
2558                     && (!Flags.ensureWallpaperInTransitions() || wc.asWallpaperToken() == null)) {
2559                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
2560                         "  Rejecting as no-op: %s", wc);
2561                 continue;
2562             }
2563             targets.add(changeInfo);
2564         }
2565         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "  Initial targets: %s",
2566                 targets.mArray);
2567         // Combine the targets from bottom to top if possible.
2568         tryPromote(targets, changes);
2569         // Establish the relationship between the targets and their top changes.
2570         populateParentChanges(targets, changes);
2571 
2572         final ArrayList<ChangeInfo> targetList = targets.getListSortedByZ();
2573         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "  Final targets: %s", targetList);
2574         return targetList;
2575     }
2576 
2577     /** Populates parent to the change info and collects intermediate targets. */
populateParentChanges(Targets targets, ArrayMap<WindowContainer, ChangeInfo> changes)2578     private static void populateParentChanges(Targets targets,
2579             ArrayMap<WindowContainer, ChangeInfo> changes) {
2580         final ArrayList<ChangeInfo> intermediates = new ArrayList<>();
2581         // Make a copy to iterate because the original array may be modified.
2582         final ArrayList<ChangeInfo> targetList = new ArrayList<>(targets.mArray.size());
2583         for (int i = targets.mArray.size() - 1; i >= 0; --i) {
2584             targetList.add(targets.mArray.valueAt(i));
2585         }
2586         for (int i = targetList.size() - 1; i >= 0; --i) {
2587             final ChangeInfo targetChange = targetList.get(i);
2588             final WindowContainer wc = targetChange.mContainer;
2589             // Wallpaper must belong to the top (regardless of how nested it is in DisplayAreas).
2590             final boolean skipIntermediateReports = isWallpaper(wc);
2591             intermediates.clear();
2592             boolean foundParentInTargets = false;
2593             // Collect the intermediate parents between target and top changed parent.
2594             for (WindowContainer<?> p = getAnimatableParent(wc); p != null;
2595                     p = getAnimatableParent(p)) {
2596                 final ChangeInfo parentChange = changes.get(p);
2597                 if (parentChange == null) {
2598                     break;
2599                 }
2600                 if (!parentChange.hasChanged()) {
2601                     // In case the target is collected after the parent has been changed, it could
2602                     // be too late to snapshot the parent change. Skip to see if there is any
2603                     // parent window further up to be considered as change parent.
2604                     continue;
2605                 }
2606                 if (p.mRemoteToken == null) {
2607                     // Intermediate parents must be those that has window to be managed by Shell.
2608                     continue;
2609                 }
2610                 if (parentChange.mEndParent != null && !skipIntermediateReports) {
2611                     targetChange.mEndParent = p;
2612                     // The chain above the parent was processed.
2613                     break;
2614                 }
2615                 if (targetList.contains(parentChange)) {
2616                     if (skipIntermediateReports) {
2617                         targetChange.mEndParent = p;
2618                     } else {
2619                         intermediates.add(parentChange);
2620                     }
2621                     // for config-at-end, we want to promote the flag based on the end-state even
2622                     // if the activity was reparented because it operates after the animation. So,
2623                     // check that here since the promote code skips reparents.
2624                     if ((targetChange.mFlags & ChangeInfo.FLAG_CHANGE_CONFIG_AT_END) != 0
2625                             && targetChange.mContainer.asActivityRecord() != null
2626                             && targetChange.mContainer.getParent() == p) {
2627                         parentChange.mFlags |= ChangeInfo.FLAG_CHANGE_CONFIG_AT_END;
2628                     }
2629                     foundParentInTargets = true;
2630                     break;
2631                 } else if (reportIfNotTop(p) && !skipIntermediateReports) {
2632                     intermediates.add(parentChange);
2633                 }
2634             }
2635             if (!foundParentInTargets || intermediates.isEmpty()) continue;
2636             // Add any always-report parents along the way.
2637             targetChange.mEndParent = intermediates.get(0).mContainer;
2638             for (int j = 0; j < intermediates.size() - 1; j++) {
2639                 final ChangeInfo intermediate = intermediates.get(j);
2640                 intermediate.mEndParent = intermediates.get(j + 1).mContainer;
2641                 targets.add(intermediate);
2642             }
2643         }
2644     }
2645 
2646     /**
2647      * Gets the leash surface for a window container.
2648      * @param t a transaction to create leashes on when necessary (fixed rotation at token-level).
2649      *          If t is null, then this will not create any leashes, just use one if it is there --
2650      *          this is relevant for building the finishTransaction since it needs to match the
2651      *          start state and not erroneously create a leash of its own.
2652      */
getLeashSurface(WindowContainer wc, @Nullable SurfaceControl.Transaction t)2653     private static SurfaceControl getLeashSurface(WindowContainer wc,
2654             @Nullable SurfaceControl.Transaction t) {
2655         final DisplayContent asDC = wc.asDisplayContent();
2656         if (asDC != null) {
2657             // DisplayContent is the "root", so we use the windowing layer instead to avoid
2658             // hardware-screen-level surfaces.
2659             return asDC.getWindowingLayer();
2660         }
2661         if (!wc.mTransitionController.useShellTransitionsRotation()) {
2662             final WindowToken asToken = wc.asWindowToken();
2663             if (asToken != null) {
2664                 // WindowTokens can have a fixed-rotation applied to them. In the current
2665                 // implementation this fact is hidden from the player, so we must create a leash.
2666                 final SurfaceControl leash = t != null ? asToken.getOrCreateFixedRotationLeash(t)
2667                         : asToken.getFixedRotationLeash();
2668                 if (leash != null) return leash;
2669             }
2670         }
2671         return wc.getSurfaceControl();
2672     }
2673 
getOrigParentSurface(WindowContainer wc)2674     private static SurfaceControl getOrigParentSurface(WindowContainer wc) {
2675         if (wc.asDisplayContent() != null) {
2676             // DisplayContent is the "root", so we reinterpret it's wc as the window layer
2677             // making the parent surface the displaycontent's surface.
2678             return wc.getSurfaceControl();
2679         }
2680         return wc.getParent().getSurfaceControl();
2681     }
2682 
2683     /**
2684      * A ready group is defined by a root window-container where all transitioning windows under
2685      * it are expected to animate together as a group. At the moment, this treats each display as
2686      * a ready-group to match the existing legacy transition behavior.
2687      */
isReadyGroup(WindowContainer wc)2688     private static boolean isReadyGroup(WindowContainer wc) {
2689         return wc instanceof DisplayContent;
2690     }
2691 
getDisplayId(@onNull WindowContainer wc)2692     private static int getDisplayId(@NonNull WindowContainer wc) {
2693         return wc.getDisplayContent() != null
2694                 ? wc.getDisplayContent().getDisplayId() : INVALID_DISPLAY;
2695     }
2696 
2697     @VisibleForTesting
calculateTransitionRoots(@onNull TransitionInfo outInfo, ArrayList<ChangeInfo> sortedTargets, @NonNull SurfaceControl.Transaction startT)2698     static void calculateTransitionRoots(@NonNull TransitionInfo outInfo,
2699             ArrayList<ChangeInfo> sortedTargets,
2700             @NonNull SurfaceControl.Transaction startT) {
2701         // There needs to be a root on each display.
2702         for (int i = 0; i < sortedTargets.size(); ++i) {
2703             final WindowContainer<?> wc = sortedTargets.get(i).mContainer;
2704             // Don't include wallpapers since they are in a different DA.
2705             if (isWallpaper(wc)) continue;
2706             final DisplayContent dc = wc.getDisplayContent();
2707             if (dc == null) continue;
2708             final int endDisplayId = dc.getDisplayId();
2709 
2710             // Check if Root was already created for this display with a higher-Z window
2711             if (outInfo.findRootIndex(endDisplayId) >= 0) continue;
2712 
2713             WindowContainer<?> ancestor = findCommonAncestor(sortedTargets, wc);
2714 
2715             // Make leash based on highest (z-order) direct child of ancestor with a participant.
2716             // Check whether the ancestor is belonged to last parent, shouldn't happen.
2717             final boolean hasReparent = !wc.isDescendantOf(ancestor);
2718             WindowContainer leashReference = wc;
2719             if (hasReparent) {
2720                 Slog.e(TAG, "Did not find common ancestor! Ancestor= " + ancestor
2721                         + " target= " + wc);
2722             } else {
2723                 while (leashReference.getParent() != ancestor) {
2724                     leashReference = leashReference.getParent();
2725                 }
2726             }
2727             final SurfaceControl rootLeash = leashReference.makeAnimationLeash().setName(
2728                     "Transition Root: " + leashReference.getName())
2729                     .setCallsite("Transition.calculateTransitionRoots").build();
2730             rootLeash.setUnreleasedWarningCallSite("Transition.calculateTransitionRoots");
2731             // Update layers to start transaction because we prevent assignment during collect, so
2732             // the layer of transition root can be correct.
2733             assignLayers(dc, startT);
2734             startT.setLayer(rootLeash, leashReference.getLastLayer());
2735             outInfo.addRootLeash(endDisplayId, rootLeash,
2736                     ancestor.getBounds().left, ancestor.getBounds().top);
2737         }
2738     }
2739 
2740     /**
2741      * Construct a TransitionInfo object from a set of targets and changes. Also populates the
2742      * root surface.
2743      * @param sortedTargets The targets sorted by z-order from top (index 0) to bottom.
2744      * @param startT The start transaction - used to set-up new leashes.
2745      */
2746     @VisibleForTesting
2747     @NonNull
calculateTransitionInfo(@ransitionType int type, int flags, ArrayList<ChangeInfo> sortedTargets, @NonNull SurfaceControl.Transaction startT)2748     static TransitionInfo calculateTransitionInfo(@TransitionType int type, int flags,
2749             ArrayList<ChangeInfo> sortedTargets,
2750             @NonNull SurfaceControl.Transaction startT) {
2751         final TransitionInfo out = new TransitionInfo(type, flags);
2752         calculateTransitionRoots(out, sortedTargets, startT);
2753         if (out.getRootCount() == 0) {
2754             return out;
2755         }
2756 
2757         final AnimationOptions animOptionsForActivityTransition =
2758                 calculateAnimationOptionsForActivityTransition(type, sortedTargets);
2759         if (!Flags.moveAnimationOptionsToChange() && animOptionsForActivityTransition != null) {
2760             out.setAnimationOptions(animOptionsForActivityTransition);
2761         }
2762 
2763         final ArraySet<WindowContainer> occludedAtEndContainers = new ArraySet<>();
2764         // Convert all the resolved ChangeInfos into TransactionInfo.Change objects in order.
2765         final int count = sortedTargets.size();
2766         for (int i = 0; i < count; ++i) {
2767             final ChangeInfo info = sortedTargets.get(i);
2768             final WindowContainer target = info.mContainer;
2769             final TransitionInfo.Change change = new TransitionInfo.Change(
2770                     target.mRemoteToken != null ? target.mRemoteToken.toWindowContainerToken()
2771                             : null, getLeashSurface(target, startT));
2772             // TODO(shell-transitions): Use leash for non-organized windows.
2773             if (info.mEndParent != null) {
2774                 change.setParent(info.mEndParent.mRemoteToken.toWindowContainerToken());
2775             }
2776             if (info.mStartParent != null && info.mStartParent.mRemoteToken != null
2777                     && target.getParent() != info.mStartParent) {
2778                 change.setLastParent(info.mStartParent.mRemoteToken.toWindowContainerToken());
2779             }
2780             change.setMode(info.getTransitMode(target));
2781             info.mReadyMode = change.getMode();
2782             change.setStartAbsBounds(info.mAbsoluteBounds);
2783             change.setFlags(info.getChangeFlags(target));
2784             info.mReadyFlags = change.getFlags();
2785             change.setDisplayId(info.mDisplayId, getDisplayId(target));
2786 
2787             // Add FLAGS_IS_OCCLUDED to preventing from visible-translucent change which belows
2788             // the non-translucent change playing unexpected open animation.
2789             if (change.getMode() == TRANSIT_TO_FRONT || change.getMode() == TRANSIT_OPEN) {
2790                 for (int occIndex = occludedAtEndContainers.size() - 1; occIndex >= 0; --occIndex) {
2791                     if (target.isDescendantOf(occludedAtEndContainers.valueAt(occIndex))) {
2792                         change.setFlags(change.getFlags() | FLAG_IS_OCCLUDED);
2793                         break;
2794                     }
2795                 }
2796             }
2797             if (!change.hasFlags(FLAG_TRANSLUCENT)  && (change.getMode() == TRANSIT_OPEN
2798                     || change.getMode() == TRANSIT_TO_FRONT
2799                     || change.getMode() == TRANSIT_CHANGE)) {
2800                 occludedAtEndContainers.add(target.getParent());
2801             }
2802 
2803             final Task task = target.asTask();
2804             final TaskFragment taskFragment = target.asTaskFragment();
2805             final boolean isEmbeddedTaskFragment = taskFragment != null
2806                     && taskFragment.isEmbedded();
2807             final ActivityRecord activityRecord = target.asActivityRecord();
2808 
2809             if (task != null) {
2810                 final ActivityManager.RunningTaskInfo tinfo = new ActivityManager.RunningTaskInfo();
2811                 task.fillTaskInfo(tinfo);
2812                 change.setTaskInfo(tinfo);
2813                 change.setRotationAnimation(getTaskRotationAnimation(task));
2814                 final ActivityRecord topRunningActivity = task.topRunningActivity();
2815                 if (topRunningActivity != null) {
2816                     if (topRunningActivity.info.supportsPictureInPicture()) {
2817                         change.setAllowEnterPip(
2818                                 topRunningActivity.checkEnterPictureInPictureAppOpsState());
2819                     }
2820                     setEndFixedRotationIfNeeded(change, task, topRunningActivity);
2821                 }
2822             } else if ((info.mFlags & ChangeInfo.FLAG_SEAMLESS_ROTATION) != 0) {
2823                 change.setRotationAnimation(ROTATION_ANIMATION_SEAMLESS);
2824             }
2825 
2826             final WindowContainer<?> parent = target.getParent();
2827             final Rect bounds = target.getBounds();
2828             final Rect parentBounds = parent.getBounds();
2829             change.setEndRelOffset(bounds.left - parentBounds.left,
2830                     bounds.top - parentBounds.top);
2831             int endRotation = target.getWindowConfiguration().getRotation();
2832             if (activityRecord != null) {
2833                 // TODO(b/227427984): Shell needs to aware letterbox.
2834                 // Always use parent bounds of activity because letterbox area (e.g. fixed aspect
2835                 // ratio or size compat mode) should be included in the animation.
2836                 change.setEndAbsBounds(parentBounds);
2837                 if (activityRecord.getRelativeDisplayRotation() != 0
2838                         && !activityRecord.mTransitionController.useShellTransitionsRotation()) {
2839                     // Use parent rotation because shell doesn't know the surface is rotated.
2840                     endRotation = parent.getWindowConfiguration().getRotation();
2841                 }
2842             } else if (isWallpaper(target) && Flags.ensureWallpaperInTransitions()
2843                     && target.getRelativeDisplayRotation() != 0
2844                     && !target.mTransitionController.useShellTransitionsRotation()) {
2845                 // If the wallpaper is "fixed-rotated", shell is unaware of this, so use the
2846                 // "as-if-not-rotating" bounds and rotation
2847                 change.setEndAbsBounds(parent.getBounds());
2848                 endRotation = parent.getWindowConfiguration().getRotation();
2849             } else {
2850                 change.setEndAbsBounds(bounds);
2851             }
2852 
2853             if (activityRecord != null || isEmbeddedTaskFragment) {
2854                 final int backgroundColor;
2855                 final TaskFragment organizedTf = activityRecord != null
2856                         ? activityRecord.getOrganizedTaskFragment()
2857                         : taskFragment.getOrganizedTaskFragment();
2858                 if (organizedTf != null && organizedTf.getAnimationParams()
2859                         .getAnimationBackgroundColor() != DEFAULT_ANIMATION_BACKGROUND_COLOR) {
2860                     // This window is embedded and has an animation background color set on the
2861                     // TaskFragment. Pass this color with this window, so the handler can use it as
2862                     // the animation background color if needed,
2863                     backgroundColor = organizedTf.getAnimationParams()
2864                             .getAnimationBackgroundColor();
2865                 } else {
2866                     // Set background color to Task theme color for activity and embedded
2867                     // TaskFragment in case we want to show background during the animation.
2868                     final Task parentTask = activityRecord != null
2869                             ? activityRecord.getTask()
2870                             : taskFragment.getTask();
2871                     backgroundColor = parentTask.getTaskDescription().getBackgroundColor();
2872                 }
2873                 // Set to opaque for animation background to prevent it from exposing the blank
2874                 // background or content below.
2875                 change.setBackgroundColor(ColorUtils.setAlphaComponent(backgroundColor, 255));
2876             }
2877 
2878             AnimationOptions animOptions = null;
2879             if (Flags.moveAnimationOptionsToChange()) {
2880                 if (activityRecord != null && animOptionsForActivityTransition != null) {
2881                     animOptions = animOptionsForActivityTransition;
2882                 } else if (Flags.activityEmbeddingOverlayPresentationFlag()
2883                         && isEmbeddedTaskFragment) {
2884                     final TaskFragmentAnimationParams params = taskFragment.getAnimationParams();
2885                     if (params.hasOverrideAnimation()) {
2886                         // Only set AnimationOptions if there's any animation override.
2887                         // We use separated field for backgroundColor, and
2888                         // AnimationOptions#backgroundColor will be removed in long term.
2889                         animOptions = AnimationOptions.makeCustomAnimOptions(
2890                                 taskFragment.getTask().getBasePackageName(),
2891                                 params.getOpenAnimationResId(), params.getChangeAnimationResId(),
2892                                 params.getCloseAnimationResId(), 0 /* backgroundColor */,
2893                                 false /* overrideTaskTransition */);
2894                     }
2895                 }
2896                 if (animOptions != null) {
2897                     change.setAnimationOptions(animOptions);
2898                 }
2899             }
2900 
2901             if (activityRecord != null) {
2902                 change.setActivityComponent(activityRecord.mActivityComponent);
2903             }
2904 
2905             change.setRotation(info.mRotation, endRotation);
2906             if (info.mSnapshot != null) {
2907                 change.setSnapshot(info.mSnapshot, info.mSnapshotLuma);
2908             }
2909 
2910             out.addChange(change);
2911         }
2912         return out;
2913     }
2914 
2915     /**
2916      * Calculates {@link AnimationOptions} for activity-to-activity transition.
2917      * It returns a valid {@link AnimationOptions} if:
2918      * <ul>
2919      *   <li>the top animation target is an Activity</li>
2920      *   <li>there's a {@link android.view.Window#setWindowAnimations(int)} and there's only
2921      *     {@link WindowState}, {@link WindowToken} and {@link ActivityRecord} target</li>
2922      * </ul>
2923      * Otherwise, it returns {@code null}.
2924      */
2925     @Nullable
calculateAnimationOptionsForActivityTransition( @ransitionType int type, @NonNull ArrayList<ChangeInfo> sortedTargets)2926     private static AnimationOptions calculateAnimationOptionsForActivityTransition(
2927             @TransitionType int type, @NonNull ArrayList<ChangeInfo> sortedTargets) {
2928         TransitionInfo.AnimationOptions animOptions = null;
2929 
2930         // Check if the top-most app is an activity (ie. activity->activity). If so, make sure
2931         // to honor its custom transition options.
2932         WindowContainer<?> topApp = null;
2933         for (int i = 0; i < sortedTargets.size(); i++) {
2934             if (isWallpaper(sortedTargets.get(i).mContainer)) continue;
2935             topApp = sortedTargets.get(i).mContainer;
2936             break;
2937         }
2938         if (topApp.asActivityRecord() != null) {
2939             final ActivityRecord topActivity = topApp.asActivityRecord();
2940             animOptions = addCustomActivityTransition(topActivity, true/* open */,
2941                     null /* animOptions */);
2942             animOptions = addCustomActivityTransition(topActivity, false/* open */,
2943                     animOptions);
2944         }
2945         final WindowManager.LayoutParams animLp =
2946                 getLayoutParamsForAnimationsStyle(type, sortedTargets);
2947         if (animLp != null && animLp.type != TYPE_APPLICATION_STARTING
2948                 && animLp.windowAnimations != 0) {
2949             // Don't send animation options if no windowAnimations have been set or if the we
2950             // are running an app starting animation, in which case we don't want the app to be
2951             // able to change its animation directly.
2952             if (animOptions != null) {
2953                 animOptions.addOptionsFromLayoutParameters(animLp);
2954             } else {
2955                 animOptions = TransitionInfo.AnimationOptions
2956                         .makeAnimOptionsFromLayoutParameters(animLp);
2957             }
2958         }
2959         return animOptions;
2960     }
2961 
2962     /**
2963      * Returns {@link TransitionInfo.AnimationOptions} with custom Activity transition appended if
2964      * {@code topActivity} specifies {@link ActivityRecord#getCustomAnimation(boolean)}, or
2965      * {@code animOptions}, otherwise.
2966      * <p>
2967      * If the passed {@code animOptions} is {@code null}, this method will creates an
2968      * {@link TransitionInfo.AnimationOptions} with custom animation appended
2969      *
2970      * @param open {@code true} to add a custom open animation, and {@false} to add a close one
2971      */
2972     @Nullable
addCustomActivityTransition( @onNull ActivityRecord activity, boolean open, @Nullable TransitionInfo.AnimationOptions animOptions)2973     private static TransitionInfo.AnimationOptions addCustomActivityTransition(
2974             @NonNull ActivityRecord activity, boolean open,
2975             @Nullable TransitionInfo.AnimationOptions animOptions) {
2976         final ActivityRecord.CustomAppTransition customAnim =
2977                 activity.getCustomAnimation(open);
2978         if (customAnim != null) {
2979             if (animOptions == null) {
2980                 animOptions = TransitionInfo.AnimationOptions
2981                         .makeCommonAnimOptions(activity.packageName);
2982             }
2983             animOptions.addCustomActivityTransition(open, customAnim.mEnterAnim,
2984                     customAnim.mExitAnim, customAnim.mBackgroundColor);
2985         }
2986         return animOptions;
2987     }
2988 
setEndFixedRotationIfNeeded(@onNull TransitionInfo.Change change, @NonNull Task task, @NonNull ActivityRecord taskTopRunning)2989     private static void setEndFixedRotationIfNeeded(@NonNull TransitionInfo.Change change,
2990             @NonNull Task task, @NonNull ActivityRecord taskTopRunning) {
2991         if (!taskTopRunning.isVisibleRequested()) {
2992             // Fixed rotation only applies to opening or changing activity.
2993             return;
2994         }
2995         if (task.inMultiWindowMode() && taskTopRunning.inMultiWindowMode()) {
2996             // Display won't be rotated for multi window Task, so the fixed rotation won't be
2997             // applied. This can happen when the windowing mode is changed before the previous
2998             // fixed rotation is applied. Check both task and activity because the activity keeps
2999             // fullscreen mode when the task is entering PiP.
3000             return;
3001         }
3002         final int taskRotation = task.getWindowConfiguration().getDisplayRotation();
3003         final int activityRotation = taskTopRunning.getWindowConfiguration()
3004                 .getDisplayRotation();
3005         // If the Activity uses fixed rotation, its rotation will be applied to display after
3006         // the current transition is done, while the Task is still in the previous rotation.
3007         if (taskRotation != activityRotation) {
3008             change.setEndFixedRotation(activityRotation);
3009             return;
3010         }
3011 
3012         // For example, the task is entering PiP so it no longer decides orientation. If the next
3013         // orientation source (it could be an activity which was behind the PiP or launching to top)
3014         // will change display rotation, then set the fixed rotation hint as well so the animation
3015         // can consider the rotated position.
3016         if (!task.inPinnedWindowingMode() || taskTopRunning.mDisplayContent.inTransition()) {
3017             return;
3018         }
3019         final WindowContainer<?> orientationSource =
3020                 taskTopRunning.mDisplayContent.getLastOrientationSource();
3021         if (orientationSource == null) {
3022             return;
3023         }
3024         final int nextRotation = orientationSource.getWindowConfiguration().getDisplayRotation();
3025         if (taskRotation != nextRotation) {
3026             change.setEndFixedRotation(nextRotation);
3027         }
3028     }
3029 
3030     /**
3031      * Finds the top-most common ancestor of app targets.
3032      *
3033      * Makes sure that the previous parent is also a descendant to make sure the animation won't
3034      * be covered by other windows below the previous parent. For example, when reparenting an
3035      * activity from PiP Task to split screen Task.
3036      */
3037     @NonNull
findCommonAncestor( @onNull ArrayList<ChangeInfo> targets, @NonNull WindowContainer<?> topApp)3038     private static WindowContainer<?> findCommonAncestor(
3039             @NonNull ArrayList<ChangeInfo> targets,
3040             @NonNull WindowContainer<?> topApp) {
3041         final int displayId = getDisplayId(topApp);
3042         WindowContainer<?> ancestor = topApp.getParent();
3043         // Go up ancestor parent chain until all targets are descendants. Ancestor should never be
3044         // null because all targets are attached.
3045         for (int i = targets.size() - 1; i >= 0; i--) {
3046             final ChangeInfo change = targets.get(i);
3047             final WindowContainer wc = change.mContainer;
3048             if (isWallpaper(wc) || getDisplayId(wc) != displayId) {
3049                 // Skip the non-app window or windows on a different display
3050                 continue;
3051             }
3052             // Re-initiate the last parent as the initial ancestor instead of the top target.
3053             // When move a leaf task from organized task to display area, try to keep the transition
3054             // root be the original organized task for close transition animation.
3055             // Otherwise, shell will use wrong root layer to play animation.
3056             // Note: Since the target is sorted, so only need to do this at the lowest target.
3057             if (change.mStartParent != null && wc.getParent() != null
3058                     && change.mStartParent.isAttached() && wc.getParent() != change.mStartParent
3059                     && i == targets.size() - 1) {
3060                 final int transitionMode = change.getTransitMode(wc);
3061                 if (transitionMode == TRANSIT_CLOSE || transitionMode == TRANSIT_TO_BACK) {
3062                     ancestor = change.mStartParent;
3063                     continue;
3064                 }
3065             }
3066             while (!wc.isDescendantOf(ancestor)) {
3067                 ancestor = ancestor.getParent();
3068             }
3069 
3070             // Make sure the previous parent is also a descendant to make sure the animation won't
3071             // be covered by other windows below the previous parent. For example, when reparenting
3072             // an activity from PiP Task to split screen Task.
3073             final WindowContainer prevParent = change.mCommonAncestor;
3074             if (prevParent == null || !prevParent.isAttached()) {
3075                 continue;
3076             }
3077             while (prevParent != ancestor && !prevParent.isDescendantOf(ancestor)) {
3078                 ancestor = ancestor.getParent();
3079             }
3080         }
3081         return ancestor;
3082     }
3083 
getLayoutParamsForAnimationsStyle(int type, ArrayList<ChangeInfo> sortedTargets)3084     private static WindowManager.LayoutParams getLayoutParamsForAnimationsStyle(int type,
3085             ArrayList<ChangeInfo> sortedTargets) {
3086         // Find the layout params of the top-most application window that is part of the
3087         // transition, which is what will control the animation theme.
3088         final ArraySet<Integer> activityTypes = new ArraySet<>();
3089         final int targetCount = sortedTargets.size();
3090         for (int i = 0; i < targetCount; ++i) {
3091             final WindowContainer target = sortedTargets.get(i).mContainer;
3092             if (target.asActivityRecord() != null) {
3093                 activityTypes.add(target.getActivityType());
3094             } else if (target.asWindowToken() == null && target.asWindowState() == null) {
3095                 // We don't want app to customize animations that are not activity to activity.
3096                 // Activity-level transitions can only include activities, wallpaper and subwindows.
3097                 // Anything else is not a WindowToken nor a WindowState and is "higher" in the
3098                 // hierarchy which means we are no longer in an activity transition.
3099                 return null;
3100             }
3101         }
3102         if (activityTypes.isEmpty()) {
3103             // We don't want app to be able to customize transitions that are not activity to
3104             // activity through the layout parameter animation style.
3105             return null;
3106         }
3107         final ActivityRecord animLpActivity =
3108                 findAnimLayoutParamsActivityRecord(sortedTargets, type, activityTypes);
3109         final WindowState mainWindow = animLpActivity != null
3110                 ? animLpActivity.findMainWindow() : null;
3111         return mainWindow != null ? mainWindow.mAttrs : null;
3112     }
3113 
findAnimLayoutParamsActivityRecord( List<ChangeInfo> sortedTargets, @TransitionType int transit, ArraySet<Integer> activityTypes)3114     private static ActivityRecord findAnimLayoutParamsActivityRecord(
3115             List<ChangeInfo> sortedTargets,
3116             @TransitionType int transit, ArraySet<Integer> activityTypes) {
3117         // Remote animations always win, but fullscreen windows override non-fullscreen windows.
3118         ActivityRecord result = lookForTopWindowWithFilter(sortedTargets,
3119                 w -> w.getRemoteAnimationDefinition() != null
3120                     && w.getRemoteAnimationDefinition().hasTransition(transit, activityTypes));
3121         if (result != null) {
3122             return result;
3123         }
3124         result = lookForTopWindowWithFilter(sortedTargets,
3125                 w -> w.fillsParent() && w.findMainWindow() != null);
3126         if (result != null) {
3127             return result;
3128         }
3129         return lookForTopWindowWithFilter(sortedTargets, w -> w.findMainWindow() != null);
3130     }
3131 
lookForTopWindowWithFilter(List<ChangeInfo> sortedTargets, Predicate<ActivityRecord> filter)3132     private static ActivityRecord lookForTopWindowWithFilter(List<ChangeInfo> sortedTargets,
3133             Predicate<ActivityRecord> filter) {
3134         final int count = sortedTargets.size();
3135         for (int i = 0; i < count; ++i) {
3136             final WindowContainer target = sortedTargets.get(i).mContainer;
3137             final ActivityRecord activityRecord = target.asTaskFragment() != null
3138                     ? target.asTaskFragment().getTopNonFinishingActivity()
3139                     : target.asActivityRecord();
3140             if (activityRecord != null && filter.test(activityRecord)) {
3141                 return activityRecord;
3142             }
3143         }
3144         return null;
3145     }
3146 
getTaskRotationAnimation(@onNull Task task)3147     private static int getTaskRotationAnimation(@NonNull Task task) {
3148         final ActivityRecord top = task.getTopVisibleActivity();
3149         if (top == null) return ROTATION_ANIMATION_UNSPECIFIED;
3150         final WindowState mainWin = top.findMainWindow(false);
3151         if (mainWin == null) return ROTATION_ANIMATION_UNSPECIFIED;
3152         int anim = mainWin.getRotationAnimationHint();
3153         if (anim >= 0) return anim;
3154         anim = mainWin.getAttrs().rotationAnimation;
3155         if (anim != ROTATION_ANIMATION_SEAMLESS) return anim;
3156         if (mainWin != task.mDisplayContent.getDisplayPolicy().getTopFullscreenOpaqueWindow()
3157                 || !top.matchParentBounds()) {
3158             // At the moment, we only support seamless rotation if there is only one window showing.
3159             return ROTATION_ANIMATION_UNSPECIFIED;
3160         }
3161         return mainWin.getAttrs().rotationAnimation;
3162     }
3163 
validateKeyguardOcclusion()3164     private void validateKeyguardOcclusion() {
3165         if ((mFlags & KEYGUARD_VISIBILITY_TRANSIT_FLAGS) != 0) {
3166             mController.mStateValidators.add(
3167                 mController.mAtm.mWindowManager.mPolicy::applyKeyguardOcclusionChange);
3168         }
3169     }
3170 
3171     /** Returns {@code true} if the display should use high performance hint for this transition. */
shouldUsePerfHint(@onNull DisplayContent dc)3172     boolean shouldUsePerfHint(@NonNull DisplayContent dc) {
3173         if (mOverrideOptions != null
3174                 && mOverrideOptions.getType() == ActivityOptions.ANIM_SCENE_TRANSITION
3175                 && mType == TRANSIT_TO_BACK && mParticipants.size() == 1) {
3176             // This should be from convertFromTranslucent that makes the occluded activity invisible
3177             // without animation. So do not use perf hint (especially early-wakeup) that may disturb
3178             // SurfaceFlinger scheduling around the last frame.
3179             return false;
3180         }
3181         return mTargetDisplays.contains(dc);
3182     }
3183 
3184     /**
3185      * Returns {@code true} if the transition and the corresponding transaction should be applied
3186      * on display thread. Currently, this only checks for display rotation change because the order
3187      * of dispatching the new display info will be after requesting the windows to sync drawing.
3188      * That avoids potential flickering of screen overlays (e.g. cutout, rounded corner). Also,
3189      * because the display thread has a higher priority, it is faster to perform the configuration
3190      * changes and window hierarchy traversal.
3191      */
shouldApplyOnDisplayThread()3192     boolean shouldApplyOnDisplayThread() {
3193         for (int i = mParticipants.size() - 1; i >= 0; --i) {
3194             final DisplayContent dc = mParticipants.valueAt(i).asDisplayContent();
3195             if (dc == null) continue;
3196             final ChangeInfo changeInfo = mChanges.get(dc);
3197             if (changeInfo != null && changeInfo.mRotation != dc.getRotation()) {
3198                 return Looper.myLooper() != mController.mAtm.mWindowManager.mH.getLooper();
3199             }
3200         }
3201         return false;
3202     }
3203 
3204     /**
3205      * Applies the new configuration for the changed displays. Returns the activities that should
3206      * check whether to deliver the new configuration to clients.
3207      */
3208     @Nullable
applyDisplayChangeIfNeeded(@onNull ArraySet<WindowContainer<?>> activitiesMayChange)3209     void applyDisplayChangeIfNeeded(@NonNull ArraySet<WindowContainer<?>> activitiesMayChange) {
3210         for (int i = mParticipants.size() - 1; i >= 0; --i) {
3211             final WindowContainer<?> wc = mParticipants.valueAt(i);
3212             final DisplayContent dc = wc.asDisplayContent();
3213             if (dc == null || !mChanges.get(dc).hasChanged()) continue;
3214             final boolean changed = dc.sendNewConfiguration();
3215             // Set to ready if no other change controls the ready state. But if there is, such as
3216             // if an activity is pausing, it will call setReady(ar, false) and wait for the next
3217             // resumed activity. Then do not set to ready because the transition only contains
3218             // partial participants. Otherwise the transition may only handle HIDE and miss OPEN.
3219             if (!mReadyTrackerOld.mUsed) {
3220                 setReady(dc, true);
3221             }
3222             if (!changed) continue;
3223             // If the update is deferred, sendNewConfiguration won't deliver new configuration to
3224             // clients, then it is the caller's responsibility to deliver the changes.
3225             if (mController.mAtm.mTaskSupervisor.isRootVisibilityUpdateDeferred()) {
3226                 dc.forAllActivities(r -> {
3227                     if (r.isVisibleRequested()) {
3228                         activitiesMayChange.add(r);
3229                     }
3230                 });
3231             }
3232         }
3233     }
3234 
getLegacyIsReady()3235     boolean getLegacyIsReady() {
3236         return isCollecting() && mSyncId >= 0;
3237     }
3238 
asyncTraceBegin(@onNull String name, int cookie)3239     static void asyncTraceBegin(@NonNull String name, int cookie) {
3240         Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_WINDOW_MANAGER, TAG, name, cookie);
3241     }
3242 
asyncTraceEnd(int cookie)3243     static void asyncTraceEnd(int cookie) {
3244         Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_WINDOW_MANAGER, TAG, cookie);
3245     }
3246 
3247     @VisibleForTesting
3248     static class ChangeInfo {
3249         private static final int FLAG_NONE = 0;
3250 
3251         /**
3252          * When set, the associated WindowContainer has been explicitly requested to be a
3253          * seamless rotation. This is currently only used by DisplayContent during fixed-rotation.
3254          */
3255         private static final int FLAG_SEAMLESS_ROTATION = 1;
3256         private static final int FLAG_TRANSIENT_LAUNCH = 2;
3257         private static final int FLAG_ABOVE_TRANSIENT_LAUNCH = 4;
3258 
3259         /** This container explicitly requested no-animation (usually Activity level). */
3260         private static final int FLAG_CHANGE_NO_ANIMATION = 0x8;
3261         /**
3262          * This container has at-least one child which IS animating (not marked NO_ANIMATION).
3263          * Used during promotion. This trumps `FLAG_NO_ANIMATION` (if both are set).
3264          */
3265         private static final int FLAG_CHANGE_YES_ANIMATION = 0x10;
3266 
3267         /** Whether this change's container moved to the top. */
3268         private static final int FLAG_CHANGE_MOVED_TO_TOP = 0x20;
3269 
3270         /** Whether this change contains config-at-end members. */
3271         private static final int FLAG_CHANGE_CONFIG_AT_END = 0x40;
3272 
3273         @IntDef(prefix = { "FLAG_" }, value = {
3274                 FLAG_NONE,
3275                 FLAG_SEAMLESS_ROTATION,
3276                 FLAG_TRANSIENT_LAUNCH,
3277                 FLAG_ABOVE_TRANSIENT_LAUNCH,
3278                 FLAG_CHANGE_NO_ANIMATION,
3279                 FLAG_CHANGE_YES_ANIMATION,
3280                 FLAG_CHANGE_MOVED_TO_TOP,
3281                 FLAG_CHANGE_CONFIG_AT_END
3282         })
3283         @Retention(RetentionPolicy.SOURCE)
3284         @interface Flag {}
3285 
3286         @NonNull final WindowContainer mContainer;
3287         /**
3288          * "Parent" that is also included in the transition. When populating the parent changes, we
3289          * may skip the intermediate parents, so this may not be the actual parent in the hierarchy.
3290          */
3291         WindowContainer mEndParent;
3292         /** Actual parent window before change state. */
3293         WindowContainer mStartParent;
3294         /**
3295          * When the window is reparented during the transition, this is the common ancestor window
3296          * of the {@link #mStartParent} and the current parent. This is needed because the
3297          * {@link #mStartParent} may have been detached when the transition starts.
3298          */
3299         WindowContainer mCommonAncestor;
3300 
3301         // State tracking
3302         boolean mExistenceChanged = false;
3303         // before change state
3304         boolean mVisible;
3305         int mWindowingMode;
3306         final Rect mAbsoluteBounds = new Rect();
3307         boolean mShowWallpaper;
3308         int mRotation = ROTATION_UNDEFINED;
3309         int mDisplayId = -1;
3310         @ActivityInfo.Config int mKnownConfigChanges;
3311 
3312         /** Extra information about this change. */
3313         @Flag int mFlags = FLAG_NONE;
3314 
3315         /** Snapshot surface and luma, if relevant. */
3316         SurfaceControl mSnapshot;
3317         float mSnapshotLuma;
3318 
3319         /** The mode which is set when the transition is ready. */
3320         @TransitionInfo.TransitionMode
3321         int mReadyMode;
3322 
3323         /** The flags which is set when the transition is ready. */
3324         @TransitionInfo.ChangeFlags
3325         int mReadyFlags;
3326 
ChangeInfo(@onNull WindowContainer origState)3327         ChangeInfo(@NonNull WindowContainer origState) {
3328             mContainer = origState;
3329             mVisible = origState.isVisibleRequested();
3330             mWindowingMode = origState.getWindowingMode();
3331             mAbsoluteBounds.set(origState.getBounds());
3332             mShowWallpaper = origState.showWallpaper();
3333             mRotation = origState.getWindowConfiguration().getRotation();
3334             mStartParent = origState.getParent();
3335             mDisplayId = getDisplayId(origState);
3336         }
3337 
3338         @VisibleForTesting
ChangeInfo(@onNull WindowContainer container, boolean visible, boolean existChange)3339         ChangeInfo(@NonNull WindowContainer container, boolean visible, boolean existChange) {
3340             this(container);
3341             mVisible = visible;
3342             mExistenceChanged = existChange;
3343             mShowWallpaper = false;
3344         }
3345 
3346         @Override
toString()3347         public String toString() {
3348             return mContainer.toString();
3349         }
3350 
hasChanged()3351         boolean hasChanged() {
3352             // the task including transient launch must promote to root task
3353             if ((mFlags & ChangeInfo.FLAG_TRANSIENT_LAUNCH) != 0
3354                     || (mFlags & ChangeInfo.FLAG_ABOVE_TRANSIENT_LAUNCH) != 0) {
3355                 return true;
3356             }
3357             // If it's invisible and hasn't changed visibility, always return false since even if
3358             // something changed, it wouldn't be a visible change.
3359             final boolean currVisible = mContainer.isVisibleRequested();
3360             if (currVisible == mVisible && !mVisible) return false;
3361             return currVisible != mVisible
3362                     || mKnownConfigChanges != 0
3363                     // if mWindowingMode is 0, this container wasn't attached at collect time, so
3364                     // assume no change in windowing-mode.
3365                     || (mWindowingMode != 0 && mContainer.getWindowingMode() != mWindowingMode)
3366                     || !mContainer.getBounds().equals(mAbsoluteBounds)
3367                     || mRotation != mContainer.getWindowConfiguration().getRotation()
3368                     || mDisplayId != getDisplayId(mContainer)
3369                     || (mFlags & ChangeInfo.FLAG_CHANGE_MOVED_TO_TOP) != 0;
3370         }
3371 
3372         @TransitionInfo.TransitionMode
getTransitMode(@onNull WindowContainer wc)3373         int getTransitMode(@NonNull WindowContainer wc) {
3374             if ((mFlags & ChangeInfo.FLAG_ABOVE_TRANSIENT_LAUNCH) != 0) {
3375                 return mExistenceChanged ? TRANSIT_CLOSE : TRANSIT_TO_BACK;
3376             }
3377             final boolean nowVisible = wc.isVisibleRequested();
3378             if (nowVisible == mVisible) {
3379                 return TRANSIT_CHANGE;
3380             }
3381             if (mExistenceChanged) {
3382                 return nowVisible ? TRANSIT_OPEN : TRANSIT_CLOSE;
3383             } else {
3384                 return nowVisible ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK;
3385             }
3386         }
3387 
3388         @TransitionInfo.ChangeFlags
getChangeFlags(@onNull WindowContainer wc)3389         int getChangeFlags(@NonNull WindowContainer wc) {
3390             int flags = 0;
3391             if (mShowWallpaper || wc.showWallpaper()) {
3392                 flags |= FLAG_SHOW_WALLPAPER;
3393             }
3394             if (isTranslucent(wc)) {
3395                 flags |= FLAG_TRANSLUCENT;
3396             }
3397             if (wc.mWmService.mAtmService.mBackNavigationController.isMonitorTransitionTarget(wc)) {
3398                 flags |= TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
3399             }
3400             final Task task = wc.asTask();
3401             if (task != null) {
3402                 final ActivityRecord topActivity = task.getTopNonFinishingActivity();
3403                 if (topActivity != null) {
3404                     if (topActivity.mStartingData != null
3405                             && topActivity.mStartingData.hasImeSurface()) {
3406                         flags |= FLAG_WILL_IME_SHOWN;
3407                     }
3408                     if (topActivity.mLaunchTaskBehind) {
3409                         Slog.e(TAG, "Unexpected launch-task-behind operation in shell transition");
3410                         flags |= FLAG_TASK_LAUNCHING_BEHIND;
3411                     }
3412                     if ((topActivity.mTransitionChangeFlags & FLAGS_IS_OCCLUDED_NO_ANIMATION)
3413                             == FLAGS_IS_OCCLUDED_NO_ANIMATION) {
3414                         flags |= FLAGS_IS_OCCLUDED_NO_ANIMATION;
3415                     }
3416                 }
3417                 if (task.voiceSession != null) {
3418                     flags |= FLAG_IS_VOICE_INTERACTION;
3419                 }
3420             }
3421             Task parentTask = null;
3422             final ActivityRecord record = wc.asActivityRecord();
3423             if (record != null) {
3424                 parentTask = record.getTask();
3425                 if (record.mVoiceInteraction) {
3426                     flags |= FLAG_IS_VOICE_INTERACTION;
3427                 }
3428                 flags |= record.mTransitionChangeFlags;
3429                 if (record.isConfigurationDispatchPaused()) {
3430                     flags |= FLAG_CONFIG_AT_END;
3431                 }
3432             }
3433             final TaskFragment taskFragment = wc.asTaskFragment();
3434             if (taskFragment != null && task == null) {
3435                 parentTask = taskFragment.getTask();
3436             }
3437             if (parentTask != null) {
3438                 if (parentTask.forAllLeafTaskFragments(TaskFragment::isEmbedded)) {
3439                     // Whether this is in a Task with embedded activity.
3440                     flags |= FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
3441                 }
3442                 final ActivityRecord starting = parentTask.topActivityContainsStartingWindow();
3443                 if (starting != null) {
3444                     if (starting == record || (starting.mStartingData != null
3445                             && starting.mStartingData.mAssociatedTask != null)) {
3446                         flags |= FLAG_IS_BEHIND_STARTING_WINDOW;
3447                     } else if (record != null && parentTask.mChildren.indexOf(record)
3448                             < parentTask.mChildren.indexOf(starting)) {
3449                         flags |= FLAG_IS_BEHIND_STARTING_WINDOW;
3450                     }
3451                 }
3452                 if (isWindowFillingTask(wc, parentTask)) {
3453                     // Whether the container fills its parent Task bounds.
3454                     flags |= FLAG_FILLS_TASK;
3455                 }
3456             } else {
3457                 final DisplayContent dc = wc.asDisplayContent();
3458                 if (dc != null) {
3459                     flags |= FLAG_IS_DISPLAY;
3460                     if (dc.hasAlertWindowSurfaces()) {
3461                         flags |= FLAG_DISPLAY_HAS_ALERT_WINDOWS;
3462                     }
3463                 } else if (isWallpaper(wc)) {
3464                     flags |= FLAG_IS_WALLPAPER;
3465                 } else if (isInputMethod(wc)) {
3466                     flags |= FLAG_IS_INPUT_METHOD;
3467                 } else {
3468                     // In this condition, the wc can only be WindowToken or DisplayArea.
3469                     final int type = wc.getWindowType();
3470                     if (type >= WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW
3471                             && type <= WindowManager.LayoutParams.LAST_SYSTEM_WINDOW) {
3472                         flags |= TransitionInfo.FLAG_IS_SYSTEM_WINDOW;
3473                     }
3474                 }
3475             }
3476             if ((mFlags & FLAG_CHANGE_NO_ANIMATION) != 0
3477                     && (mFlags & FLAG_CHANGE_YES_ANIMATION) == 0) {
3478                 flags |= FLAG_NO_ANIMATION;
3479             }
3480             if ((mFlags & FLAG_CHANGE_MOVED_TO_TOP) != 0) {
3481                 flags |= FLAG_MOVED_TO_TOP;
3482             }
3483             if ((mFlags & FLAG_CHANGE_CONFIG_AT_END) != 0) {
3484                 flags |= FLAG_CONFIG_AT_END;
3485             }
3486             return flags;
3487         }
3488 
3489         /** Whether the container fills its parent Task bounds before and after the transition. */
isWindowFillingTask(@onNull WindowContainer wc, @NonNull Task parentTask)3490         private boolean isWindowFillingTask(@NonNull WindowContainer wc, @NonNull Task parentTask) {
3491             final Rect taskBounds = parentTask.getBounds();
3492             final int taskWidth = taskBounds.width();
3493             final int taskHeight = taskBounds.height();
3494             final Rect startBounds = mAbsoluteBounds;
3495             final Rect endBounds = wc.getBounds();
3496             // Treat it as filling the task if it is not visible.
3497             final boolean isInvisibleOrFillingTaskBeforeTransition = !mVisible
3498                     || (taskWidth == startBounds.width() && taskHeight == startBounds.height());
3499             final boolean isInVisibleOrFillingTaskAfterTransition = !wc.isVisibleRequested()
3500                     || (taskWidth == endBounds.width() && taskHeight == endBounds.height());
3501             return isInvisibleOrFillingTaskBeforeTransition
3502                     && isInVisibleOrFillingTaskAfterTransition;
3503         }
3504     }
3505 
3506     /**
3507      * This transition will be considered not-ready until a corresponding call to
3508      * {@link #continueTransitionReady}
3509      */
deferTransitionReady()3510     void deferTransitionReady() {
3511         ++mReadyTrackerOld.mDeferReadyDepth;
3512         // Make sure it wait until #continueTransitionReady() is called.
3513         mSyncEngine.setReady(mSyncId, false);
3514     }
3515 
3516     /** This undoes one call to {@link #deferTransitionReady}. */
continueTransitionReady()3517     void continueTransitionReady() {
3518         --mReadyTrackerOld.mDeferReadyDepth;
3519         // Apply ready in case it is waiting for the previous defer call.
3520         applyReady();
3521     }
3522 
3523     @Override
onReadyTimeout()3524     public void onReadyTimeout() {
3525         if (!mController.useFullReadyTracking()) {
3526             Slog.e(TAG, "#" + mSyncId + " readiness timeout, used=" + mReadyTrackerOld.mUsed
3527                     + " deferReadyDepth=" + mReadyTrackerOld.mDeferReadyDepth
3528                     + " group=" + mReadyTrackerOld.mReadyGroups);
3529             return;
3530         }
3531         Slog.e(TAG, "#" + mSyncId + " met conditions: " + mReadyTracker.mMet);
3532         Slog.e(TAG, "#" + mSyncId + " unmet conditions: " + mReadyTracker.mConditions);
3533     }
3534 
3535     /**
3536      * Represents a condition that must be met before an associated transition can be considered
3537      * ready.
3538      *
3539      * Expected usage is that a ReadyCondition is created and then attached to a transition's
3540      * ReadyTracker via {@link ReadyTracker#add}. After that, it is expected to monitor the state
3541      * of the system and when the condition it represents is met, it will call
3542      * {@link ReadyTracker#meet}.
3543      *
3544      * This base class is a simple explicit, named condition. A caller will create/attach the
3545      * condition and then explicitly call {@link #meet} on it (which internally calls
3546      * {@link ReadyTracker#meet}.
3547      *
3548      * Example:
3549      * <pre>
3550      *     ReadyCondition myCondition = new ReadyCondition("my condition");
3551      *     transitionController.waitFor(myCondition);
3552      *     ... Some operations ...
3553      *     myCondition.meet();
3554      * </pre>
3555      */
3556     static class ReadyCondition {
3557         final String mName;
3558 
3559         /** Just used for debugging */
3560         final Object mDebugTarget;
3561         ReadyTracker mTracker;
3562         boolean mMet = false;
3563 
3564         /** If set (non-null), then this is met by another reason besides state (eg. timeout). */
3565         String mAlternate = null;
3566 
ReadyCondition(@onNull String name)3567         ReadyCondition(@NonNull String name) {
3568             mName = name;
3569             mDebugTarget = null;
3570         }
3571 
ReadyCondition(@onNull String name, @Nullable Object debugTarget)3572         ReadyCondition(@NonNull String name, @Nullable Object debugTarget) {
3573             mName = name;
3574             mDebugTarget = debugTarget;
3575         }
3576 
getDebugRep()3577         protected String getDebugRep() {
3578             if (mDebugTarget != null) {
3579                 return mName + ":" + mDebugTarget;
3580             }
3581             return mName;
3582         }
3583 
3584         @Override
toString()3585         public String toString() {
3586             return "{" + getDebugRep() + (mAlternate != null ? " (" + mAlternate + ")" : "") + "}";
3587         }
3588 
3589         /**
3590          * Instructs this condition to start tracking system state to detect when this is met.
3591          * Don't call this directly; it is called when this object is attached to a transition's
3592          * ready-tracker.
3593          */
startTracking()3594         void startTracking() {
3595         }
3596 
3597         /**
3598          * Immediately consider this condition met by an alternative reason (one which doesn't
3599          * match the normal intent of this condition -- eg. a timeout).
3600          */
meetAlternate(@onNull String reason)3601         void meetAlternate(@NonNull String reason) {
3602             if (mMet) return;
3603             mAlternate = reason;
3604             meet();
3605         }
3606 
3607         /** Immediately consider this condition met. */
meet()3608         void meet() {
3609             if (mMet) return;
3610             if (mTracker == null) {
3611                 throw new IllegalStateException("Can't meet a condition before it is waited on");
3612             }
3613             mTracker.meet(this);
3614         }
3615     }
3616 
3617     static class ReadyTracker {
3618         /**
3619          * Used as a place-holder in situations where the transition system isn't active (such as
3620          * early-boot, mid shell crash/recovery, or when using legacy).
3621          */
3622         static final ReadyTracker NULL_TRACKER = new ReadyTracker(null);
3623 
3624         private final Transition mTransition;
3625 
3626         /** List of conditions that are still being waited on. */
3627         final ArrayList<ReadyCondition> mConditions = new ArrayList<>();
3628 
3629         /** List of already-met conditions. Fully-qualified for debugging. */
3630         final ArrayList<ReadyCondition> mMet = new ArrayList<>();
3631 
ReadyTracker(Transition transition)3632         ReadyTracker(Transition transition) {
3633             mTransition = transition;
3634         }
3635 
add(@onNull ReadyCondition condition)3636         void add(@NonNull ReadyCondition condition) {
3637             if (mTransition == null || !mTransition.mController.useFullReadyTracking()) {
3638                 condition.mTracker = NULL_TRACKER;
3639                 return;
3640             }
3641             mConditions.add(condition);
3642             condition.mTracker = this;
3643             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Add condition %s for #%d",
3644                     condition, mTransition.mSyncId);
3645             condition.startTracking();
3646         }
3647 
meet(@onNull ReadyCondition condition)3648         void meet(@NonNull ReadyCondition condition) {
3649             if (mTransition == null || !mTransition.mController.useFullReadyTracking()) return;
3650             if (mTransition.mState >= STATE_PLAYING) {
3651                 Slog.w(TAG, "#%d: Condition met too late, already in state=" + mTransition.mState
3652                         + ": " + condition);
3653                 return;
3654             }
3655             if (!mConditions.remove(condition)) {
3656                 if (mMet.contains(condition)) {
3657                     throw new IllegalStateException("Can't meet the same condition more than once: "
3658                             + condition + " #" + mTransition.mSyncId);
3659                 } else {
3660                     throw new IllegalArgumentException("Can't meet a condition that isn't being "
3661                             + "waited on: " + condition + " in #" + mTransition.mSyncId);
3662                 }
3663             }
3664             condition.mMet = true;
3665             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Met condition %s for #%d (%d"
3666                     + " left)", condition, mTransition.mSyncId, mConditions.size());
3667             mMet.add(condition);
3668             mTransition.applyReady();
3669         }
3670 
isReady()3671         boolean isReady() {
3672             return mConditions.isEmpty() && !mMet.isEmpty();
3673         }
3674     }
3675 
3676     /**
3677      * The transition sync mechanism has 2 parts:
3678      *   1. Whether all WM operations for a particular transition are "ready" (eg. did the app
3679      *      launch or stop or get a new configuration?).
3680      *   2. Whether all the windows involved have finished drawing their final-state content.
3681      *
3682      * A transition animation can play once both parts are complete. This ready-tracker keeps track
3683      * of part (1). Currently, WM code assumes that "readiness" (part 1) is grouped. This means that
3684      * even if the WM operations in one group are ready, the whole transition itself may not be
3685      * ready if there are WM operations still pending in another group. This class helps keep track
3686      * of readiness across the multiple groups. Currently, we assume that each display is a group
3687      * since that is how it has been until now.
3688      */
3689     private static class ReadyTrackerOld {
3690         private final ArrayMap<WindowContainer, Boolean> mReadyGroups = new ArrayMap<>();
3691 
3692         /**
3693          * Ensures that this doesn't report as allReady before it has been used. This is needed
3694          * in very niche cases where a transition is a no-op (nothing has been collected) but we
3695          * still want to be marked ready (via. setAllReady).
3696          */
3697         private boolean mUsed = false;
3698 
3699         /**
3700          * If true, this overrides all ready groups and reports ready. Used by shell-initiated
3701          * transitions via {@link #setAllReady()}.
3702          */
3703         private boolean mReadyOverride = false;
3704 
3705         /**
3706          * When non-zero, this transition is forced not-ready (even over setAllReady()). Use this
3707          * (via deferTransitionReady/continueTransitionReady) for situations where we want to do
3708          * bulk operations which could trigger surface-placement but the existing ready-state
3709          * isn't known.
3710          */
3711         private int mDeferReadyDepth = 0;
3712 
3713         /**
3714          * Adds a ready-group. Any setReady calls in this subtree will be tracked together. For
3715          * now these are only DisplayContents.
3716          */
addGroup(WindowContainer wc)3717         void addGroup(WindowContainer wc) {
3718             if (mReadyGroups.containsKey(wc)) {
3719                 return;
3720             }
3721             mReadyGroups.put(wc, false);
3722         }
3723 
3724         /**
3725          * Sets a group's ready state.
3726          * @param wc Any container within a group's subtree. Used to identify the ready-group.
3727          */
setReadyFrom(WindowContainer wc, boolean ready)3728         void setReadyFrom(WindowContainer wc, boolean ready) {
3729             mUsed = true;
3730             WindowContainer current = wc;
3731             while (current != null) {
3732                 if (isReadyGroup(current)) {
3733                     mReadyGroups.put(current, ready);
3734                     ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Setting Ready-group to"
3735                             + " %b. group=%s from %s", ready, current, wc);
3736                     break;
3737                 }
3738                 current = current.getParent();
3739             }
3740         }
3741 
3742         /** Marks this as ready regardless of individual groups. */
setAllReady()3743         void setAllReady() {
3744             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Setting allReady override");
3745             mUsed = true;
3746             mReadyOverride = true;
3747         }
3748 
3749         /** @return true if all tracked subtrees are ready. */
allReady()3750         boolean allReady() {
3751             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " allReady query: used=%b "
3752                     + "override=%b defer=%d states=[%s]", mUsed, mReadyOverride, mDeferReadyDepth,
3753                     groupsToString());
3754             // If the readiness has never been touched, mUsed will be false. We never want to
3755             // consider a transition ready if nothing has been reported on it.
3756             if (!mUsed) return false;
3757             // If we are deferring readiness, we never report ready. This is usually temporary.
3758             if (mDeferReadyDepth > 0) return false;
3759             // Next check all the ready groups to see if they are ready. We can short-cut this if
3760             // ready-override is set (which is treated as "everything is marked ready").
3761             if (mReadyOverride) return true;
3762             for (int i = mReadyGroups.size() - 1; i >= 0; --i) {
3763                 final WindowContainer wc = mReadyGroups.keyAt(i);
3764                 if (!wc.isAttached() || !wc.isVisibleRequested()) continue;
3765                 if (!mReadyGroups.valueAt(i)) return false;
3766             }
3767             return true;
3768         }
3769 
groupsToString()3770         private String groupsToString() {
3771             StringBuilder b = new StringBuilder();
3772             for (int i = 0; i < mReadyGroups.size(); ++i) {
3773                 if (i != 0) b.append(',');
3774                 b.append(mReadyGroups.keyAt(i)).append(':')
3775                         .append(mReadyGroups.valueAt(i));
3776             }
3777             return b.toString();
3778         }
3779     }
3780 
3781     /**
3782      * The container to represent the depth relation for calculating transition targets. The window
3783      * container with larger depth is put at larger index. For the same depth, higher z-order has
3784      * larger index.
3785      */
3786     private static class Targets {
3787         /** All targets. Its keys (depth) are sorted in ascending order naturally. */
3788         final SparseArray<ChangeInfo> mArray = new SparseArray<>();
3789         /** The targets which were represented by their parent. */
3790         private ArrayList<ChangeInfo> mRemovedTargets;
3791         private int mDepthFactor;
3792 
add(ChangeInfo target)3793         void add(ChangeInfo target) {
3794             // The number of slots per depth is larger than the total number of window container,
3795             // so the depth score (key) won't have collision.
3796             if (mDepthFactor == 0) {
3797                 mDepthFactor = target.mContainer.mWmService.mRoot.getTreeWeight() + 1;
3798             }
3799             int score = target.mContainer.getPrefixOrderIndex();
3800             WindowContainer<?> wc = target.mContainer;
3801             while (wc != null) {
3802                 final WindowContainer<?> parent = wc.getParent();
3803                 if (parent != null) {
3804                     score += mDepthFactor;
3805                 }
3806                 wc = parent;
3807             }
3808             mArray.put(score, target);
3809         }
3810 
remove(int index)3811         void remove(int index) {
3812             final ChangeInfo removingTarget = mArray.valueAt(index);
3813             mArray.removeAt(index);
3814             if (mRemovedTargets == null) {
3815                 mRemovedTargets = new ArrayList<>();
3816             }
3817             mRemovedTargets.add(removingTarget);
3818         }
3819 
wasParticipated(ChangeInfo wc)3820         boolean wasParticipated(ChangeInfo wc) {
3821             return mArray.indexOfValue(wc) >= 0
3822                     || (mRemovedTargets != null && mRemovedTargets.contains(wc));
3823         }
3824 
3825         /** Returns the target list sorted by z-order in ascending order (index 0 is top). */
getListSortedByZ()3826         ArrayList<ChangeInfo> getListSortedByZ() {
3827             final SparseArray<ChangeInfo> arrayByZ = new SparseArray<>(mArray.size());
3828             for (int i = mArray.size() - 1; i >= 0; --i) {
3829                 final int zOrder = mArray.keyAt(i) % mDepthFactor;
3830                 arrayByZ.put(zOrder, mArray.valueAt(i));
3831             }
3832             final ArrayList<ChangeInfo> sortedTargets = new ArrayList<>(arrayByZ.size());
3833             for (int i = arrayByZ.size() - 1; i >= 0; --i) {
3834                 sortedTargets.add(arrayByZ.valueAt(i));
3835             }
3836             return sortedTargets;
3837         }
3838     }
3839 
3840     /**
3841      * Interface for freezing a container's content during sync preparation. Really just one impl
3842      * but broken into an interface for testing (since you can't take screenshots in unit tests).
3843      */
3844     interface IContainerFreezer {
3845         /**
3846          * Makes sure a particular window is "frozen" for the remainder of a sync.
3847          *
3848          * @return whether the freeze was successful. It fails if `wc` is already in a frozen window
3849          *         or is not visible/ready.
3850          */
freeze(@onNull WindowContainer wc, @NonNull Rect bounds)3851         boolean freeze(@NonNull WindowContainer wc, @NonNull Rect bounds);
3852 
3853         /** Populates `t` with operations that clean-up any state created to set-up the freeze. */
cleanUp(SurfaceControl.Transaction t)3854         void cleanUp(SurfaceControl.Transaction t);
3855     }
3856 
3857     /**
3858      * Freezes container content by taking a screenshot. Because screenshots are heavy, usage of
3859      * any container "freeze" is currently explicit. WM code needs to be prudent about which
3860      * containers to freeze.
3861      */
3862     @VisibleForTesting
3863     private class ScreenshotFreezer implements IContainerFreezer {
3864         /** Keeps track of which windows are frozen. Not all frozen windows have snapshots. */
3865         private final ArraySet<WindowContainer> mFrozen = new ArraySet<>();
3866 
3867         /** Takes a screenshot and puts it at the top of the container's surface. */
3868         @Override
freeze(@onNull WindowContainer wc, @NonNull Rect bounds)3869         public boolean freeze(@NonNull WindowContainer wc, @NonNull Rect bounds) {
3870             if (!wc.isVisibleRequested()) return false;
3871 
3872             // Check if any parents have already been "frozen". If so, `wc` is already part of that
3873             // snapshot, so just skip it.
3874             for (WindowContainer p = wc; p != null; p = p.getParent()) {
3875                 if (mFrozen.contains(p)) return false;
3876             }
3877 
3878             if (mIsSeamlessRotation) {
3879                 WindowState top = wc.getDisplayContent() == null ? null
3880                         : wc.getDisplayContent().getDisplayPolicy().getTopFullscreenOpaqueWindow();
3881                 if (top != null && (top == wc || top.isDescendantOf(wc))) {
3882                     // Don't use screenshots for seamless windows: these will use BLAST even if not
3883                     // BLAST mode.
3884                     mFrozen.add(wc);
3885                     return true;
3886                 }
3887             }
3888 
3889             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Screenshotting %s [%s]",
3890                     wc.toString(), bounds.toString());
3891 
3892             Rect cropBounds = new Rect(bounds);
3893             cropBounds.offsetTo(0, 0);
3894             final boolean isDisplayRotation = wc.asDisplayContent() != null
3895                     && wc.asDisplayContent().isRotationChanging();
3896             ScreenCapture.LayerCaptureArgs captureArgs =
3897                     new ScreenCapture.LayerCaptureArgs.Builder(wc.getSurfaceControl())
3898                             .setSourceCrop(cropBounds)
3899                             .setCaptureSecureLayers(true)
3900                             .setAllowProtected(true)
3901                             .setHintForSeamlessTransition(isDisplayRotation)
3902                             .build();
3903             ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer =
3904                     ScreenCapture.captureLayers(captureArgs);
3905             final HardwareBuffer buffer = screenshotBuffer == null ? null
3906                     : screenshotBuffer.getHardwareBuffer();
3907             if (buffer == null || buffer.getWidth() <= 1 || buffer.getHeight() <= 1) {
3908                 // This can happen when display is not ready.
3909                 Slog.w(TAG, "Failed to capture screenshot for " + wc);
3910                 return false;
3911             }
3912             // Some tests may check the name "RotationLayer" to detect display rotation.
3913             final String name = isDisplayRotation ? "RotationLayer" : "transition snapshot: " + wc;
3914             SurfaceControl snapshotSurface = wc.makeAnimationLeash()
3915                     .setName(name)
3916                     .setOpaque(wc.fillsParent())
3917                     .setParent(wc.getSurfaceControl())
3918                     .setSecure(screenshotBuffer.containsSecureLayers())
3919                     .setCallsite("Transition.ScreenshotSync")
3920                     .setBLASTLayer()
3921                     .build();
3922             mFrozen.add(wc);
3923             final ChangeInfo changeInfo = Objects.requireNonNull(mChanges.get(wc));
3924             changeInfo.mSnapshot = snapshotSurface;
3925             if (changeInfo.mRotation != wc.mDisplayContent.getRotation()) {
3926                 // This isn't cheap, so only do it for rotation change.
3927                 changeInfo.mSnapshotLuma = TransitionAnimation.getBorderLuma(
3928                         buffer, screenshotBuffer.getColorSpace(), wc.mSurfaceControl);
3929             }
3930             SurfaceControl.Transaction t = wc.mWmService.mTransactionFactory.get();
3931             TransitionAnimation.configureScreenshotLayer(t, snapshotSurface, screenshotBuffer);
3932             t.show(snapshotSurface);
3933 
3934             // Place it on top of anything else in the container.
3935             t.setLayer(snapshotSurface, Integer.MAX_VALUE);
3936             t.apply();
3937             t.close();
3938             buffer.close();
3939 
3940             // Detach the screenshot on the sync transaction (the screenshot is just meant to
3941             // freeze the window until the sync transaction is applied (with all its other
3942             // corresponding changes), so this is how we unfreeze it.
3943             wc.getSyncTransaction().reparent(snapshotSurface, null /* newParent */);
3944             return true;
3945         }
3946 
3947         @Override
cleanUp(SurfaceControl.Transaction t)3948         public void cleanUp(SurfaceControl.Transaction t) {
3949             for (int i = 0; i < mFrozen.size(); ++i) {
3950                 SurfaceControl snap =
3951                         Objects.requireNonNull(mChanges.get(mFrozen.valueAt(i))).mSnapshot;
3952                 // May be null if it was frozen via BLAST override.
3953                 if (snap == null) continue;
3954                 t.reparent(snap, null /* newParent */);
3955             }
3956         }
3957     }
3958 
3959     private static class Token extends Binder {
3960         final WeakReference<Transition> mTransition;
3961 
Token(Transition transition)3962         Token(Transition transition) {
3963             mTransition = new WeakReference<>(transition);
3964         }
3965 
3966         @Override
toString()3967         public String toString() {
3968             return "Token{" + Integer.toHexString(System.identityHashCode(this)) + " "
3969                     + mTransition.get() + "}";
3970         }
3971     }
3972 }
3973