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