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