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 
1378         mState = STATE_FINISHED;
1379         // Rotation change may be deferred while there is a display change transition, so check
1380         // again in case there is a new pending change.
1381         if (hasParticipatedDisplay && !mController.useShellTransitionsRotation()) {
1382             mController.mAtm.mWindowManager.updateRotation(false /* alwaysSendConfiguration */,
1383                     false /* forceRelayout */);
1384         }
1385         cleanUpInternal();
1386         mController.updateAnimatingState(mTmpTransaction);
1387         mTmpTransaction.apply();
1388 
1389         // Handle back animation if it's already started.
1390         mController.mAtm.mBackNavigationController.onTransitionFinish(mTargets, this);
1391         mController.mFinishingTransition = null;
1392         mController.mSnapshotController.onTransitionFinish(mType, mTargets);
1393     }
1394 
abort()1395     void abort() {
1396         // This calls back into itself via controller.abort, so just early return here.
1397         if (mState == STATE_ABORT) return;
1398         if (mState == STATE_PENDING) {
1399             // hasn't started collecting, so can jump directly to aborted state.
1400             mState = STATE_ABORT;
1401             return;
1402         }
1403         if (mState != STATE_COLLECTING && mState != STATE_STARTED) {
1404             throw new IllegalStateException("Too late to abort. state=" + mState);
1405         }
1406         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Aborting Transition: %d", mSyncId);
1407         mState = STATE_ABORT;
1408         mLogger.mAbortTimeNs = SystemClock.elapsedRealtimeNanos();
1409         mController.mTransitionTracer.logAbortedTransition(this);
1410         // Syncengine abort will call through to onTransactionReady()
1411         mSyncEngine.abort(mSyncId);
1412         mController.dispatchLegacyAppTransitionCancelled();
1413     }
1414 
1415     /** Immediately moves this to playing even if it isn't started yet. */
playNow()1416     void playNow() {
1417         if (!(mState == STATE_COLLECTING || mState == STATE_STARTED)) {
1418             return;
1419         }
1420         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Force Playing Transition: %d",
1421                 mSyncId);
1422         mForcePlaying = true;
1423         setAllReady();
1424         if (mState == STATE_COLLECTING) {
1425             start();
1426         }
1427         // Don't wait for actual surface-placement. We don't want anything else collected in this
1428         // transition.
1429         mSyncEngine.onSurfacePlacement();
1430     }
1431 
isForcePlaying()1432     boolean isForcePlaying() {
1433         return mForcePlaying;
1434     }
1435 
setRemoteAnimationApp(IApplicationThread app)1436     void setRemoteAnimationApp(IApplicationThread app) {
1437         mRemoteAnimApp = app;
1438     }
1439 
1440     /** Returns the app which will run the transition animation. */
getRemoteAnimationApp()1441     IApplicationThread getRemoteAnimationApp() {
1442         return mRemoteAnimApp;
1443     }
1444 
setNoAnimation(WindowContainer wc)1445     void setNoAnimation(WindowContainer wc) {
1446         final ChangeInfo change = mChanges.get(wc);
1447         if (change == null) {
1448             throw new IllegalStateException("Can't set no-animation property of non-participant");
1449         }
1450         change.mFlags |= ChangeInfo.FLAG_CHANGE_NO_ANIMATION;
1451     }
1452 
1453     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
containsChangeFor(WindowContainer wc, ArrayList<ChangeInfo> list)1454     static boolean containsChangeFor(WindowContainer wc, ArrayList<ChangeInfo> list) {
1455         for (int i = list.size() - 1; i >= 0; --i) {
1456             if (list.get(i).mContainer == wc) return true;
1457         }
1458         return false;
1459     }
1460 
1461     @Override
onTransactionReady(int syncId, SurfaceControl.Transaction transaction)1462     public void onTransactionReady(int syncId, SurfaceControl.Transaction transaction) {
1463         if (syncId != mSyncId) {
1464             Slog.e(TAG, "Unexpected Sync ID " + syncId + ". Expected " + mSyncId);
1465             return;
1466         }
1467 
1468         // Commit the visibility of visible activities before calculateTransitionInfo(), so the
1469         // TaskInfo can be visible. Also it needs to be done before moveToPlaying(), otherwise
1470         // ActivityRecord#canShowWindows() may reject to show its window. The visibility also
1471         // needs to be updated for STATE_ABORT.
1472         commitVisibleActivities(transaction);
1473         commitVisibleWallpapers();
1474 
1475         // Fall-back to the default display if there isn't one participating.
1476         final DisplayContent primaryDisplay = !mTargetDisplays.isEmpty() ? mTargetDisplays.get(0)
1477                 : mController.mAtm.mRootWindowContainer.getDefaultDisplay();
1478 
1479         if (mState == STATE_ABORT) {
1480             mController.onAbort(this);
1481             primaryDisplay.getPendingTransaction().merge(transaction);
1482             mSyncId = -1;
1483             mOverrideOptions = null;
1484             cleanUpInternal();
1485             return;
1486         }
1487 
1488         if (mState != STATE_STARTED) {
1489             Slog.e(TAG, "Playing a Transition which hasn't started! #" + mSyncId + " This will "
1490                     + "likely cause an exception in Shell");
1491         }
1492 
1493         mState = STATE_PLAYING;
1494         mStartTransaction = transaction;
1495         mFinishTransaction = mController.mAtm.mWindowManager.mTransactionFactory.get();
1496 
1497         // Flags must be assigned before calculateTransitionInfo. Otherwise it won't take effect.
1498         if (primaryDisplay.isKeyguardLocked()) {
1499             mFlags |= TRANSIT_FLAG_KEYGUARD_LOCKED;
1500         }
1501 
1502         // This is the only (or last) transition that is collecting, so we need to report any
1503         // leftover order changes.
1504         collectOrderChanges(mController.mWaitingTransitions.isEmpty());
1505 
1506         if (mPriorVisibilityMightBeDirty) {
1507             updatePriorVisibility();
1508         }
1509         // Resolve the animating targets from the participants.
1510         mTargets = calculateTargets(mParticipants, mChanges);
1511 
1512         // Check whether the participants were animated from back navigation.
1513         mController.mAtm.mBackNavigationController.onTransactionReady(this, mTargets,
1514                 transaction);
1515         final TransitionInfo info = calculateTransitionInfo(mType, mFlags, mTargets, transaction);
1516         info.setDebugId(mSyncId);
1517         mController.assignTrack(this, info);
1518 
1519         mController.moveToPlaying(this);
1520 
1521         // Repopulate the displays based on the resolved targets.
1522         mTargetDisplays.clear();
1523         for (int i = 0; i < info.getRootCount(); ++i) {
1524             final DisplayContent dc = mController.mAtm.mRootWindowContainer.getDisplayContent(
1525                     info.getRoot(i).getDisplayId());
1526             mTargetDisplays.add(dc);
1527         }
1528 
1529         for (int i = 0; i < mTargets.size(); ++i) {
1530             final DisplayArea da = mTargets.get(i).mContainer.asDisplayArea();
1531             if (da == null) continue;
1532             if (da.isVisibleRequested()) {
1533                 mController.mValidateDisplayVis.remove(da);
1534             } else {
1535                 // In case something accidentally hides a displayarea and nothing shows it again.
1536                 mController.mValidateDisplayVis.add(da);
1537             }
1538         }
1539 
1540         if (mOverrideOptions != null) {
1541             info.setAnimationOptions(mOverrideOptions);
1542             if (mOverrideOptions.getType() == ANIM_OPEN_CROSS_PROFILE_APPS) {
1543                 for (int i = 0; i < mTargets.size(); ++i) {
1544                     final TransitionInfo.Change c = info.getChanges().get(i);
1545                     final ActivityRecord ar = mTargets.get(i).mContainer.asActivityRecord();
1546                     if (ar == null || c.getMode() != TRANSIT_OPEN) continue;
1547                     int flags = c.getFlags();
1548                     flags |= ar.mUserId == ar.mWmService.mCurrentUserId
1549                             ? TransitionInfo.FLAG_CROSS_PROFILE_OWNER_THUMBNAIL
1550                             : TransitionInfo.FLAG_CROSS_PROFILE_WORK_THUMBNAIL;
1551                     c.setFlags(flags);
1552                     break;
1553                 }
1554             }
1555         }
1556 
1557         // TODO(b/188669821): Move to animation impl in shell.
1558         for (int i = 0; i < mTargetDisplays.size(); ++i) {
1559             handleLegacyRecentsStartBehavior(mTargetDisplays.get(i), info);
1560             if (mRecentsDisplayId != INVALID_DISPLAY) break;
1561         }
1562 
1563         // The callback is only populated for custom activity-level client animations
1564         sendRemoteCallback(mClientAnimationStartCallback);
1565 
1566         // Manually show any activities that are visibleRequested. This is needed to properly
1567         // support simultaneous animation queueing/merging. Specifically, if transition A makes
1568         // an activity invisible, it's finishTransaction (which is applied *after* the animation)
1569         // will hide the activity surface. If transition B then makes the activity visible again,
1570         // the normal surfaceplacement logic won't add a show to this start transaction because
1571         // the activity visibility hasn't been committed yet. To deal with this, we have to manually
1572         // show here in the same way that we manually hide in finishTransaction.
1573         for (int i = mParticipants.size() - 1; i >= 0; --i) {
1574             final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord();
1575             if (ar == null || !ar.isVisibleRequested()) continue;
1576             transaction.show(ar.getSurfaceControl());
1577 
1578             // Also manually show any non-reported parents. This is necessary in a few cases
1579             // where a task is NOT organized but had its visibility changed within its direct
1580             // parent. An example of this is if an alternate home leaf-task HB is started atop the
1581             // normal home leaf-task HA: these are both in the Home root-task HR, so there will be a
1582             // transition containing HA and HB where HA surface is hidden. If a standard task SA is
1583             // launched on top, then HB finishes, no transition will happen since neither home is
1584             // visible. When SA finishes, the transition contains HR rather than HA. Since home
1585             // leaf-tasks are NOT organized, HA won't be in the transition and thus its surface
1586             // wouldn't be shown. Just show is safe here since all other properties will have
1587             // already been reset by the original hiding-transition's finishTransaction (we can't
1588             // show in the finishTransaction because by then the activity doesn't hide until
1589             // surface placement).
1590             for (WindowContainer p = ar.getParent(); p != null && !containsChangeFor(p, mTargets);
1591                     p = p.getParent()) {
1592                 if (p.getSurfaceControl() != null) {
1593                     transaction.show(p.getSurfaceControl());
1594                 }
1595             }
1596         }
1597 
1598         // Record windowtokens (activity/wallpaper) that are expected to be visible after the
1599         // transition animation. This will be used in finishTransition to prevent prematurely
1600         // committing visibility. Skip transient launches since those are only temporarily visible.
1601         if (mTransientLaunches == null) {
1602             for (int i = mParticipants.size() - 1; i >= 0; --i) {
1603                 final WindowContainer wc = mParticipants.valueAt(i);
1604                 if (wc.asWindowToken() == null || !wc.isVisibleRequested()) continue;
1605                 mVisibleAtTransitionEndTokens.add(wc.asWindowToken());
1606             }
1607         }
1608 
1609         // Take task snapshots before the animation so that we can capture IME before it gets
1610         // transferred. If transition is transient, IME won't be moved during the transition and
1611         // the tasks are still live, so we take the snapshot at the end of the transition instead.
1612         if (mTransientLaunches == null) {
1613             mController.mSnapshotController.onTransactionReady(mType, mTargets);
1614         }
1615 
1616         // This is non-null only if display has changes. It handles the visible windows that don't
1617         // need to be participated in the transition.
1618         for (int i = 0; i < mTargetDisplays.size(); ++i) {
1619             final DisplayContent dc = mTargetDisplays.get(i);
1620             final AsyncRotationController controller = dc.getAsyncRotationController();
1621             if (controller != null && containsChangeFor(dc, mTargets)) {
1622                 controller.setupStartTransaction(transaction);
1623             }
1624         }
1625         buildFinishTransaction(mFinishTransaction, info);
1626         mCleanupTransaction = mController.mAtm.mWindowManager.mTransactionFactory.get();
1627         buildCleanupTransaction(mCleanupTransaction, info);
1628         if (mController.getTransitionPlayer() != null && mIsPlayerEnabled) {
1629             mController.dispatchLegacyAppTransitionStarting(info, mStatusBarTransitionDelay);
1630             try {
1631                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
1632                         "Calling onTransitionReady: %s", info);
1633                 mLogger.mSendTimeNs = SystemClock.elapsedRealtimeNanos();
1634                 mLogger.mInfo = info;
1635                 mController.getTransitionPlayer().onTransitionReady(
1636                         mToken, info, transaction, mFinishTransaction);
1637                 if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
1638                     asyncTraceBegin(TRACE_NAME_PLAY_TRANSITION, System.identityHashCode(this));
1639                 }
1640             } catch (RemoteException e) {
1641                 // If there's an exception when trying to send the mergedTransaction to the
1642                 // client, we should finish and apply it here so the transactions aren't lost.
1643                 postCleanupOnFailure();
1644             }
1645             for (int i = 0; i < mTargetDisplays.size(); ++i) {
1646                 final DisplayContent dc = mTargetDisplays.get(i);
1647                 final AccessibilityController accessibilityController =
1648                         dc.mWmService.mAccessibilityController;
1649                 if (accessibilityController.hasCallbacks()) {
1650                     accessibilityController.onWMTransition(dc.getDisplayId(), mType);
1651                 }
1652             }
1653         } else {
1654             // No player registered or it's not enabled, so just finish/apply immediately
1655             if (!mIsPlayerEnabled) {
1656                 mLogger.mSendTimeNs = SystemClock.uptimeNanos();
1657                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Apply and finish immediately"
1658                         + " because player is disabled for transition #%d .", mSyncId);
1659             }
1660             postCleanupOnFailure();
1661         }
1662         mOverrideOptions = null;
1663 
1664         reportStartReasonsToLogger();
1665 
1666         // Since we created root-leash but no longer reference it from core, release it now
1667         info.releaseAnimSurfaces();
1668 
1669         mController.mLoggerHandler.post(mLogger::logOnSend);
1670         if (mLogger.mInfo != null) {
1671             mController.mTransitionTracer.logSentTransition(this, mTargets);
1672         }
1673     }
1674 
1675     /**
1676      * Collect tasks which moved-to-top as part of this transition. This also updates the
1677      * controller's latest-reported when relevant.
1678      *
1679      * This is a non-trivial operation because transition can collect in parallel; however, it can
1680      * be made tenable by acknowledging that the "setup" part of collection (phase 1) is still
1681      * globally serial; so, we can build some reasonable rules around it.
1682      *
1683      * First, we record the "start" on-top state (to compare against). Then, when this becomes
1684      * ready (via allReady, NOT onTransactionReady), we also record the "onReady" on-top state
1685      * -- the idea here is that upon "allReady", all the actual WM changes should be done and we
1686      * are now just waiting for window content to become ready (finish drawing).
1687      *
1688      * Then, in this function (during onTransactionReady), we compare the two orders and include
1689      * any changes to the order in the reported transition-info. Unfortunately, because of parallel
1690      * collection, the order can change in unexpected ways by now. To resolve this, we ALSO keep a
1691      * global "latest reported order" in TransitionController and use that to make decisions.
1692      */
1693     @VisibleForTesting
collectOrderChanges(boolean reportCurrent)1694     void collectOrderChanges(boolean reportCurrent) {
1695         if (mOnTopTasksStart.isEmpty()) return;
1696         boolean includesOrderChange = false;
1697         for (int i = 0; i < mOnTopTasksAtReady.size(); ++i) {
1698             final Task task = mOnTopTasksAtReady.get(i);
1699             if (mOnTopTasksStart.contains(task)) continue;
1700             includesOrderChange = true;
1701             break;
1702         }
1703         if (!includesOrderChange && !reportCurrent) {
1704             // This transition doesn't include an order change, so if it isn't required to report
1705             // the current focus (eg. it's the last of a cluster of transitions), then don't
1706             // report.
1707             return;
1708         }
1709         // The transition included an order change, but it may not be up-to-date, so grab the
1710         // latest state and compare with the last reported state (or our start state if no
1711         // reported state exists).
1712         ArrayList<Task> onTopTasksEnd = new ArrayList<>();
1713         for (int d = 0; d < mTargetDisplays.size(); ++d) {
1714             addOnTopTasks(mTargetDisplays.get(d), onTopTasksEnd);
1715             final int displayId = mTargetDisplays.get(d).mDisplayId;
1716             ArrayList<Task> reportedOnTop = mController.mLatestOnTopTasksReported.get(displayId);
1717             for (int i = onTopTasksEnd.size() - 1; i >= 0; --i) {
1718                 final Task task = onTopTasksEnd.get(i);
1719                 if (task.getDisplayId() != displayId) continue;
1720                 // If it didn't change since last report, don't report
1721                 if (reportedOnTop == null) {
1722                     if (mOnTopTasksStart.contains(task)) continue;
1723                 } else if (reportedOnTop.contains(task)) {
1724                     continue;
1725                 }
1726                 // Need to report it.
1727                 mParticipants.add(task);
1728                 int changeIdx = mChanges.indexOfKey(task);
1729                 if (changeIdx < 0) {
1730                     mChanges.put(task, new ChangeInfo(task));
1731                     changeIdx = mChanges.indexOfKey(task);
1732                 }
1733                 mChanges.valueAt(changeIdx).mFlags |= ChangeInfo.FLAG_CHANGE_MOVED_TO_TOP;
1734             }
1735             // Swap in the latest on-top tasks.
1736             mController.mLatestOnTopTasksReported.put(displayId, onTopTasksEnd);
1737             onTopTasksEnd = reportedOnTop != null ? reportedOnTop : new ArrayList<>();
1738             onTopTasksEnd.clear();
1739         }
1740     }
1741 
postCleanupOnFailure()1742     private void postCleanupOnFailure() {
1743         mController.mAtm.mH.post(() -> {
1744             synchronized (mController.mAtm.mGlobalLock) {
1745                 cleanUpOnFailure();
1746             }
1747         });
1748     }
1749 
1750     /**
1751      * If the remote failed for any reason, use this to do any appropriate clean-up. Do not call
1752      * this directly, it's designed to by called by {@link TransitionController} only.
1753      */
cleanUpOnFailure()1754     void cleanUpOnFailure() {
1755         // No need to clean-up if this isn't playing yet.
1756         if (mState < STATE_PLAYING) return;
1757 
1758         if (mStartTransaction != null) {
1759             mStartTransaction.apply();
1760         }
1761         if (mFinishTransaction != null) {
1762             mFinishTransaction.apply();
1763         }
1764         mController.finishTransition(this);
1765     }
1766 
cleanUpInternal()1767     private void cleanUpInternal() {
1768         // Clean-up any native references.
1769         for (int i = 0; i < mChanges.size(); ++i) {
1770             final ChangeInfo ci = mChanges.valueAt(i);
1771             if (ci.mSnapshot != null) {
1772                 ci.mSnapshot.release();
1773             }
1774         }
1775         if (mCleanupTransaction != null) {
1776             mCleanupTransaction.apply();
1777             mCleanupTransaction = null;
1778         }
1779     }
1780 
1781     /** The transition is ready to play. Make the start transaction show the surfaces. */
commitVisibleActivities(SurfaceControl.Transaction transaction)1782     private void commitVisibleActivities(SurfaceControl.Transaction transaction) {
1783         for (int i = mParticipants.size() - 1; i >= 0; --i) {
1784             final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord();
1785             if (ar == null || ar.getTask() == null) {
1786                 continue;
1787             }
1788             if (ar.isVisibleRequested()) {
1789                 ar.commitVisibility(true /* visible */, false /* performLayout */,
1790                         true /* fromTransition */);
1791                 ar.commitFinishDrawing(transaction);
1792             }
1793             ar.getTask().setDeferTaskAppear(false);
1794         }
1795     }
1796 
1797     /**
1798      * Reset waitingToshow for all wallpapers, and commit the visibility of the visible ones
1799      */
commitVisibleWallpapers()1800     private void commitVisibleWallpapers() {
1801         boolean showWallpaper = shouldWallpaperBeVisible();
1802         for (int i = mParticipants.size() - 1; i >= 0; --i) {
1803             final WallpaperWindowToken wallpaper = mParticipants.valueAt(i).asWallpaperToken();
1804             if (wallpaper != null) {
1805                 wallpaper.waitingToShow = false;
1806                 if (!wallpaper.isVisible() && wallpaper.isVisibleRequested()) {
1807                     wallpaper.commitVisibility(showWallpaper);
1808                 }
1809             }
1810         }
1811     }
1812 
shouldWallpaperBeVisible()1813     private boolean shouldWallpaperBeVisible() {
1814         for (int i = mParticipants.size() - 1; i >= 0; --i) {
1815             WindowContainer participant = mParticipants.valueAt(i);
1816             if (participant.showWallpaper()) return true;
1817         }
1818         return false;
1819     }
1820 
1821     // TODO(b/188595497): Remove after migrating to shell.
1822     /** @see RecentsAnimationController#attachNavigationBarToApp */
handleLegacyRecentsStartBehavior(DisplayContent dc, TransitionInfo info)1823     private void handleLegacyRecentsStartBehavior(DisplayContent dc, TransitionInfo info) {
1824         if ((mFlags & TRANSIT_FLAG_IS_RECENTS) == 0) {
1825             return;
1826         }
1827 
1828         // Recents has an input-consumer to grab input from the "live tile" app. Set that up here
1829         final InputConsumerImpl recentsAnimationInputConsumer =
1830                 dc.getInputMonitor().getInputConsumer(INPUT_CONSUMER_RECENTS_ANIMATION);
1831         ActivityRecord recentsActivity = null;
1832         if (recentsAnimationInputConsumer != null) {
1833             // find the top-most going-away activity and the recents activity. The top-most
1834             // is used as layer reference while the recents is used for registering the consumer
1835             // override.
1836             ActivityRecord topActivity = null;
1837             for (int i = 0; i < info.getChanges().size(); ++i) {
1838                 final TransitionInfo.Change change = info.getChanges().get(i);
1839                 if (change.getTaskInfo() == null) continue;
1840                 final Task task = Task.fromWindowContainerToken(
1841                         info.getChanges().get(i).getTaskInfo().token);
1842                 if (task == null) continue;
1843                 final int activityType = change.getTaskInfo().topActivityType;
1844                 final boolean isRecents = activityType == ACTIVITY_TYPE_HOME
1845                         || activityType == ACTIVITY_TYPE_RECENTS;
1846                 if (isRecents && recentsActivity == null) {
1847                     recentsActivity = task.getTopVisibleActivity();
1848                 } else if (!isRecents && topActivity == null) {
1849                     topActivity = task.getTopNonFinishingActivity();
1850                 }
1851             }
1852             if (recentsActivity != null && topActivity != null) {
1853                 recentsAnimationInputConsumer.mWindowHandle.touchableRegion.set(
1854                         topActivity.getBounds());
1855                 dc.getInputMonitor().setActiveRecents(recentsActivity, topActivity);
1856             }
1857         }
1858 
1859         if (recentsActivity == null) {
1860             // No recents activity on `dc`, its probably on a different display.
1861             return;
1862         }
1863         mRecentsDisplayId = dc.mDisplayId;
1864 
1865         // The rest of this function handles nav-bar reparenting
1866 
1867         if (!dc.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition()
1868                 // Skip the case where the nav bar is controlled by fade rotation.
1869                 || dc.getAsyncRotationController() != null) {
1870             return;
1871         }
1872 
1873         WindowContainer topWC = null;
1874         // Find the top-most non-home, closing app.
1875         for (int i = 0; i < info.getChanges().size(); ++i) {
1876             final TransitionInfo.Change c = info.getChanges().get(i);
1877             if (c.getTaskInfo() == null || c.getTaskInfo().displayId != mRecentsDisplayId
1878                     || c.getTaskInfo().getActivityType() != ACTIVITY_TYPE_STANDARD
1879                     || !(c.getMode() == TRANSIT_CLOSE || c.getMode() == TRANSIT_TO_BACK)) {
1880                 continue;
1881             }
1882             topWC = WindowContainer.fromBinder(c.getContainer().asBinder());
1883             break;
1884         }
1885         if (topWC == null || topWC.inMultiWindowMode()) {
1886             return;
1887         }
1888 
1889         final WindowState navWindow = dc.getDisplayPolicy().getNavigationBar();
1890         if (navWindow == null || navWindow.mToken == null) {
1891             return;
1892         }
1893         mController.mNavigationBarAttachedToApp = true;
1894         navWindow.mToken.cancelAnimation();
1895         final SurfaceControl.Transaction t = navWindow.mToken.getPendingTransaction();
1896         final SurfaceControl navSurfaceControl = navWindow.mToken.getSurfaceControl();
1897         t.reparent(navSurfaceControl, topWC.getSurfaceControl());
1898         t.show(navSurfaceControl);
1899 
1900         final WindowContainer imeContainer = dc.getImeContainer();
1901         if (imeContainer.isVisible()) {
1902             t.setRelativeLayer(navSurfaceControl, imeContainer.getSurfaceControl(), 1);
1903         } else {
1904             // Place the nav bar on top of anything else in the top activity.
1905             t.setLayer(navSurfaceControl, Integer.MAX_VALUE);
1906         }
1907         final StatusBarManagerInternal bar = dc.getDisplayPolicy().getStatusBarManagerInternal();
1908         if (bar != null) {
1909             bar.setNavigationBarLumaSamplingEnabled(mRecentsDisplayId, false);
1910         }
1911     }
1912 
1913     /** @see RecentsAnimationController#restoreNavigationBarFromApp */
legacyRestoreNavigationBarFromApp()1914     void legacyRestoreNavigationBarFromApp() {
1915         if (!mController.mNavigationBarAttachedToApp) {
1916             return;
1917         }
1918         mController.mNavigationBarAttachedToApp = false;
1919 
1920         if (mRecentsDisplayId == INVALID_DISPLAY) {
1921             Slog.e(TAG, "Reparented navigation bar without a valid display");
1922             mRecentsDisplayId = DEFAULT_DISPLAY;
1923         }
1924 
1925         final DisplayContent dc =
1926                 mController.mAtm.mRootWindowContainer.getDisplayContent(mRecentsDisplayId);
1927         final StatusBarManagerInternal bar = dc.getDisplayPolicy().getStatusBarManagerInternal();
1928         if (bar != null) {
1929             bar.setNavigationBarLumaSamplingEnabled(mRecentsDisplayId, true);
1930         }
1931         final WindowState navWindow = dc.getDisplayPolicy().getNavigationBar();
1932         if (navWindow == null) return;
1933         navWindow.setSurfaceTranslationY(0);
1934 
1935         final WindowToken navToken = navWindow.mToken;
1936         if (navToken == null) return;
1937         final SurfaceControl.Transaction t = dc.getPendingTransaction();
1938         final WindowContainer parent = navToken.getParent();
1939         t.setLayer(navToken.getSurfaceControl(), navToken.getLastLayer());
1940 
1941         boolean animate = false;
1942         // Search for the home task. If it is supposed to be visible, then the navbar is not at
1943         // the bottom of the screen, so we need to animate it.
1944         for (int i = 0; i < mTargets.size(); ++i) {
1945             final Task task = mTargets.get(i).mContainer.asTask();
1946             if (task == null || !task.isActivityTypeHomeOrRecents()) continue;
1947             animate = task.isVisibleRequested();
1948             break;
1949         }
1950 
1951         if (animate) {
1952             final NavBarFadeAnimationController controller =
1953                     new NavBarFadeAnimationController(dc);
1954             controller.fadeWindowToken(true);
1955         } else {
1956             // Reparent the SurfaceControl of nav bar token back.
1957             t.reparent(navToken.getSurfaceControl(), parent.getSurfaceControl());
1958         }
1959 
1960         // To apply transactions.
1961         dc.mWmService.scheduleAnimationLocked();
1962     }
1963 
reportStartReasonsToLogger()1964     private void reportStartReasonsToLogger() {
1965         // Record transition start in metrics logger. We just assume everything is "DRAWN"
1966         // at this point since splash-screen is a presentation (shell) detail.
1967         ArrayMap<WindowContainer, Integer> reasons = new ArrayMap<>();
1968         for (int i = mParticipants.size() - 1; i >= 0; --i) {
1969             ActivityRecord r = mParticipants.valueAt(i).asActivityRecord();
1970             if (r == null || !r.isVisibleRequested()) continue;
1971             int transitionReason = APP_TRANSITION_WINDOWS_DRAWN;
1972             // At this point, r is "ready", but if it's not "ALL ready" then it is probably only
1973             // ready due to starting-window.
1974             if (r.mStartingData instanceof SplashScreenStartingData && !r.mLastAllReadyAtSync) {
1975                 transitionReason = APP_TRANSITION_SPLASH_SCREEN;
1976             } else if (r.isActivityTypeHomeOrRecents() && isTransientLaunch(r)) {
1977                 transitionReason = APP_TRANSITION_RECENTS_ANIM;
1978             }
1979             reasons.put(r, transitionReason);
1980         }
1981         mController.mAtm.mTaskSupervisor.getActivityMetricsLogger().notifyTransitionStarting(
1982                 reasons);
1983     }
1984 
1985     @Override
toString()1986     public String toString() {
1987         StringBuilder sb = new StringBuilder(64);
1988         sb.append("TransitionRecord{");
1989         sb.append(Integer.toHexString(System.identityHashCode(this)));
1990         sb.append(" id=" + mSyncId);
1991         sb.append(" type=" + transitTypeToString(mType));
1992         sb.append(" flags=0x" + Integer.toHexString(mFlags));
1993         sb.append('}');
1994         return sb.toString();
1995     }
1996 
1997     /** Returns the parent that the remote animator can animate or control. */
getAnimatableParent(WindowContainer<?> wc)1998     private static WindowContainer<?> getAnimatableParent(WindowContainer<?> wc) {
1999         WindowContainer<?> parent = wc.getParent();
2000         while (parent != null
2001                 && (!parent.canCreateRemoteAnimationTarget() && !parent.isOrganized())) {
2002             parent = parent.getParent();
2003         }
2004         return parent;
2005     }
2006 
reportIfNotTop(WindowContainer wc)2007     private static boolean reportIfNotTop(WindowContainer wc) {
2008         // Organized tasks need to be reported anyways because Core won't show() their surfaces
2009         // and we can't rely on onTaskAppeared because it isn't in sync.
2010         // TODO(shell-transitions): switch onTaskAppeared usage over to transitions OPEN.
2011         return wc.isOrganized();
2012     }
2013 
isWallpaper(WindowContainer wc)2014     private static boolean isWallpaper(WindowContainer wc) {
2015         return wc.asWallpaperToken() != null;
2016     }
2017 
isInputMethod(WindowContainer wc)2018     private static boolean isInputMethod(WindowContainer wc) {
2019         return wc.getWindowType() == TYPE_INPUT_METHOD;
2020     }
2021 
occludesKeyguard(WindowContainer wc)2022     private static boolean occludesKeyguard(WindowContainer wc) {
2023         final ActivityRecord ar = wc.asActivityRecord();
2024         if (ar != null) {
2025             return ar.canShowWhenLocked();
2026         }
2027         final Task t = wc.asTask();
2028         if (t != null) {
2029             // Get the top activity which was visible (since this is going away, it will remain
2030             // client visible until the transition is finished).
2031             // skip hidden (or about to hide) apps
2032             final ActivityRecord top = t.getActivity(WindowToken::isClientVisible);
2033             return top != null && top.canShowWhenLocked();
2034         }
2035         return false;
2036     }
2037 
isTranslucent(@onNull WindowContainer wc)2038     private static boolean isTranslucent(@NonNull WindowContainer wc) {
2039         final TaskFragment taskFragment = wc.asTaskFragment();
2040         if (taskFragment == null) {
2041             return !wc.fillsParent();
2042         }
2043 
2044         // Check containers differently as they are affected by child visibility.
2045 
2046         if (taskFragment.isTranslucentForTransition()) {
2047             // TaskFragment doesn't contain occluded ActivityRecord.
2048             return true;
2049         }
2050         final TaskFragment adjacentTaskFragment = taskFragment.getAdjacentTaskFragment();
2051         if (adjacentTaskFragment != null) {
2052             // When the TaskFragment has an adjacent TaskFragment, sibling behind them should be
2053             // hidden unless any of them are translucent.
2054             return adjacentTaskFragment.isTranslucentForTransition();
2055         } else {
2056             // Non-filling without adjacent is considered as translucent.
2057             return !wc.fillsParent();
2058         }
2059     }
2060 
updatePriorVisibility()2061     private void updatePriorVisibility() {
2062         for (int i = 0; i < mChanges.size(); ++i) {
2063             final ChangeInfo chg = mChanges.valueAt(i);
2064             // For task/activity, recalculate the current "real" visibility.
2065             if (chg.mContainer.asActivityRecord() == null && chg.mContainer.asTask() == null) {
2066                 continue;
2067             }
2068             // This ONLY works in the visible -> invisible case (and is only needed for this case)
2069             // because commitVisible(false) is deferred until finish.
2070             if (!chg.mVisible) continue;
2071             chg.mVisible = chg.mContainer.isVisible();
2072         }
2073     }
2074 
2075     /**
2076      * Under some conditions (eg. all visible targets within a parent container are transitioning
2077      * the same way) the transition can be "promoted" to the parent container. This means an
2078      * animation can play just on the parent rather than all the individual children.
2079      *
2080      * @return {@code true} if transition in target can be promoted to its parent.
2081      */
canPromote(ChangeInfo targetChange, Targets targets, ArrayMap<WindowContainer, ChangeInfo> changes)2082     private static boolean canPromote(ChangeInfo targetChange, Targets targets,
2083             ArrayMap<WindowContainer, ChangeInfo> changes) {
2084         final WindowContainer<?> target = targetChange.mContainer;
2085         final WindowContainer<?> parent = target.getParent();
2086         final ChangeInfo parentChange = changes.get(parent);
2087         if (!parent.canCreateRemoteAnimationTarget()
2088                 || parentChange == null || !parentChange.hasChanged()) {
2089             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "      SKIP: %s",
2090                     "parent can't be target " + parent);
2091             return false;
2092         }
2093         if (isWallpaper(target)) {
2094             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "      SKIP: is wallpaper");
2095             return false;
2096         }
2097 
2098         if (targetChange.mStartParent != null && target.getParent() != targetChange.mStartParent) {
2099             // When a window is reparented, the state change won't fit into any of the parents.
2100             // Don't promote such change so that we can animate the reparent if needed.
2101             return false;
2102         }
2103 
2104         final @TransitionInfo.TransitionMode int mode = targetChange.getTransitMode(target);
2105         for (int i = parent.getChildCount() - 1; i >= 0; --i) {
2106             final WindowContainer<?> sibling = parent.getChildAt(i);
2107             if (target == sibling) continue;
2108             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "      check sibling %s",
2109                     sibling);
2110             final ChangeInfo siblingChange = changes.get(sibling);
2111             if (siblingChange == null || !targets.wasParticipated(siblingChange)) {
2112                 if (sibling.isVisibleRequested()) {
2113                     // Sibling is visible but not animating, so no promote.
2114                     ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
2115                             "        SKIP: sibling is visible but not part of transition");
2116                     return false;
2117                 }
2118                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
2119                         "        unrelated invisible sibling %s", sibling);
2120                 continue;
2121             }
2122 
2123             final int siblingMode = siblingChange.getTransitMode(sibling);
2124             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
2125                     "        sibling is a participant with mode %s",
2126                     TransitionInfo.modeToString(siblingMode));
2127             if (reduceMode(mode) != reduceMode(siblingMode)) {
2128                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
2129                         "          SKIP: common mode mismatch. was %s",
2130                         TransitionInfo.modeToString(mode));
2131                 return false;
2132             }
2133         }
2134         return true;
2135     }
2136 
2137     /** "reduces" a mode into a smaller set of modes that uniquely represents visibility change. */
2138     @TransitionInfo.TransitionMode
reduceMode(@ransitionInfo.TransitionMode int mode)2139     private static int reduceMode(@TransitionInfo.TransitionMode int mode) {
2140         switch (mode) {
2141             case TRANSIT_TO_BACK: return TRANSIT_CLOSE;
2142             case TRANSIT_TO_FRONT: return TRANSIT_OPEN;
2143             default: return mode;
2144         }
2145     }
2146 
2147     /**
2148      * Go through topTargets and try to promote (see {@link #canPromote}) one of them.
2149      *
2150      * @param targets all targets that will be sent to the player.
2151      */
tryPromote(Targets targets, ArrayMap<WindowContainer, ChangeInfo> changes)2152     private static void tryPromote(Targets targets, ArrayMap<WindowContainer, ChangeInfo> changes) {
2153         WindowContainer<?> lastNonPromotableParent = null;
2154         // Go through from the deepest target.
2155         for (int i = targets.mArray.size() - 1; i >= 0; --i) {
2156             final ChangeInfo targetChange = targets.mArray.valueAt(i);
2157             final WindowContainer<?> target = targetChange.mContainer;
2158             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "    checking %s", target);
2159             final WindowContainer<?> parent = target.getParent();
2160             if (parent == lastNonPromotableParent) {
2161                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
2162                         "      SKIP: its sibling was rejected");
2163                 continue;
2164             }
2165             if (!canPromote(targetChange, targets, changes)) {
2166                 lastNonPromotableParent = parent;
2167                 continue;
2168             }
2169             if (reportIfNotTop(target)) {
2170                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
2171                         "        keep as target %s", target);
2172             } else {
2173                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
2174                         "        remove from targets %s", target);
2175                 targets.remove(i);
2176             }
2177             final ChangeInfo parentChange = changes.get(parent);
2178             if (targets.mArray.indexOfValue(parentChange) < 0) {
2179                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
2180                         "      CAN PROMOTE: promoting to parent %s", parent);
2181                 // The parent has lower depth, so it will be checked in the later iteration.
2182                 i++;
2183                 targets.add(parentChange);
2184             }
2185             if ((targetChange.mFlags & ChangeInfo.FLAG_CHANGE_NO_ANIMATION) != 0) {
2186                 parentChange.mFlags |= ChangeInfo.FLAG_CHANGE_NO_ANIMATION;
2187             } else {
2188                 parentChange.mFlags |= ChangeInfo.FLAG_CHANGE_YES_ANIMATION;
2189             }
2190         }
2191     }
2192 
2193     /**
2194      * Find WindowContainers to be animated from a set of opening and closing apps. We will promote
2195      * animation targets to higher level in the window hierarchy if possible.
2196      */
2197     @VisibleForTesting
2198     @NonNull
calculateTargets(ArraySet<WindowContainer> participants, ArrayMap<WindowContainer, ChangeInfo> changes)2199     static ArrayList<ChangeInfo> calculateTargets(ArraySet<WindowContainer> participants,
2200             ArrayMap<WindowContainer, ChangeInfo> changes) {
2201         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
2202                 "Start calculating TransitionInfo based on participants: %s", participants);
2203 
2204         // Add all valid participants to the target container.
2205         final Targets targets = new Targets();
2206         for (int i = participants.size() - 1; i >= 0; --i) {
2207             final WindowContainer<?> wc = participants.valueAt(i);
2208             if (!wc.isAttached()) {
2209                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
2210                         "  Rejecting as detached: %s", wc);
2211                 continue;
2212             }
2213             // The level of transition target should be at least window token.
2214             if (wc.asWindowState() != null) continue;
2215 
2216             final ChangeInfo changeInfo = changes.get(wc);
2217 
2218             // Reject no-ops
2219             if (!changeInfo.hasChanged()) {
2220                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
2221                         "  Rejecting as no-op: %s", wc);
2222                 continue;
2223             }
2224             targets.add(changeInfo);
2225         }
2226         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "  Initial targets: %s",
2227                 targets.mArray);
2228         // Combine the targets from bottom to top if possible.
2229         tryPromote(targets, changes);
2230         // Establish the relationship between the targets and their top changes.
2231         populateParentChanges(targets, changes);
2232 
2233         final ArrayList<ChangeInfo> targetList = targets.getListSortedByZ();
2234         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "  Final targets: %s", targetList);
2235         return targetList;
2236     }
2237 
2238     /** Populates parent to the change info and collects intermediate targets. */
populateParentChanges(Targets targets, ArrayMap<WindowContainer, ChangeInfo> changes)2239     private static void populateParentChanges(Targets targets,
2240             ArrayMap<WindowContainer, ChangeInfo> changes) {
2241         final ArrayList<ChangeInfo> intermediates = new ArrayList<>();
2242         // Make a copy to iterate because the original array may be modified.
2243         final ArrayList<ChangeInfo> targetList = new ArrayList<>(targets.mArray.size());
2244         for (int i = targets.mArray.size() - 1; i >= 0; --i) {
2245             targetList.add(targets.mArray.valueAt(i));
2246         }
2247         for (int i = targetList.size() - 1; i >= 0; --i) {
2248             final ChangeInfo targetChange = targetList.get(i);
2249             final WindowContainer wc = targetChange.mContainer;
2250             // Wallpaper must belong to the top (regardless of how nested it is in DisplayAreas).
2251             final boolean skipIntermediateReports = isWallpaper(wc);
2252             intermediates.clear();
2253             boolean foundParentInTargets = false;
2254             // Collect the intermediate parents between target and top changed parent.
2255             for (WindowContainer<?> p = getAnimatableParent(wc); p != null;
2256                     p = getAnimatableParent(p)) {
2257                 final ChangeInfo parentChange = changes.get(p);
2258                 if (parentChange == null || !parentChange.hasChanged()) break;
2259                 if (p.mRemoteToken == null) {
2260                     // Intermediate parents must be those that has window to be managed by Shell.
2261                     continue;
2262                 }
2263                 if (parentChange.mEndParent != null && !skipIntermediateReports) {
2264                     targetChange.mEndParent = p;
2265                     // The chain above the parent was processed.
2266                     break;
2267                 }
2268                 if (targetList.contains(parentChange)) {
2269                     if (skipIntermediateReports) {
2270                         targetChange.mEndParent = p;
2271                     } else {
2272                         intermediates.add(parentChange);
2273                     }
2274                     foundParentInTargets = true;
2275                     break;
2276                 } else if (reportIfNotTop(p) && !skipIntermediateReports) {
2277                     intermediates.add(parentChange);
2278                 }
2279             }
2280             if (!foundParentInTargets || intermediates.isEmpty()) continue;
2281             // Add any always-report parents along the way.
2282             targetChange.mEndParent = intermediates.get(0).mContainer;
2283             for (int j = 0; j < intermediates.size() - 1; j++) {
2284                 final ChangeInfo intermediate = intermediates.get(j);
2285                 intermediate.mEndParent = intermediates.get(j + 1).mContainer;
2286                 targets.add(intermediate);
2287             }
2288         }
2289     }
2290 
2291     /**
2292      * Gets the leash surface for a window container.
2293      * @param t a transaction to create leashes on when necessary (fixed rotation at token-level).
2294      *          If t is null, then this will not create any leashes, just use one if it is there --
2295      *          this is relevant for building the finishTransaction since it needs to match the
2296      *          start state and not erroneously create a leash of its own.
2297      */
getLeashSurface(WindowContainer wc, @Nullable SurfaceControl.Transaction t)2298     private static SurfaceControl getLeashSurface(WindowContainer wc,
2299             @Nullable SurfaceControl.Transaction t) {
2300         final DisplayContent asDC = wc.asDisplayContent();
2301         if (asDC != null) {
2302             // DisplayContent is the "root", so we use the windowing layer instead to avoid
2303             // hardware-screen-level surfaces.
2304             return asDC.getWindowingLayer();
2305         }
2306         if (!wc.mTransitionController.useShellTransitionsRotation()) {
2307             final WindowToken asToken = wc.asWindowToken();
2308             if (asToken != null) {
2309                 // WindowTokens can have a fixed-rotation applied to them. In the current
2310                 // implementation this fact is hidden from the player, so we must create a leash.
2311                 final SurfaceControl leash = t != null ? asToken.getOrCreateFixedRotationLeash(t)
2312                         : asToken.getFixedRotationLeash();
2313                 if (leash != null) return leash;
2314             }
2315         }
2316         return wc.getSurfaceControl();
2317     }
2318 
getOrigParentSurface(WindowContainer wc)2319     private static SurfaceControl getOrigParentSurface(WindowContainer wc) {
2320         if (wc.asDisplayContent() != null) {
2321             // DisplayContent is the "root", so we reinterpret it's wc as the window layer
2322             // making the parent surface the displaycontent's surface.
2323             return wc.getSurfaceControl();
2324         } else if (wc.getParent().asDisplayContent() != null) {
2325             // DisplayContent is kinda split into 2 pieces, the "real root" and the
2326             // "windowing layer". So if the parent of the window is DC, then it really belongs on
2327             // the windowing layer (unless it's an overlay display area, but those can't be in
2328             // transitions anyways).
2329             return wc.getParent().asDisplayContent().getWindowingLayer();
2330         }
2331         return wc.getParent().getSurfaceControl();
2332     }
2333 
2334     /**
2335      * A ready group is defined by a root window-container where all transitioning windows under
2336      * it are expected to animate together as a group. At the moment, this treats each display as
2337      * a ready-group to match the existing legacy transition behavior.
2338      */
isReadyGroup(WindowContainer wc)2339     private static boolean isReadyGroup(WindowContainer wc) {
2340         return wc instanceof DisplayContent;
2341     }
2342 
getDisplayId(@onNull WindowContainer wc)2343     private static int getDisplayId(@NonNull WindowContainer wc) {
2344         return wc.getDisplayContent() != null
2345                 ? wc.getDisplayContent().getDisplayId() : INVALID_DISPLAY;
2346     }
2347 
2348     @VisibleForTesting
calculateTransitionRoots(@onNull TransitionInfo outInfo, ArrayList<ChangeInfo> sortedTargets, @NonNull SurfaceControl.Transaction startT)2349     static void calculateTransitionRoots(@NonNull TransitionInfo outInfo,
2350             ArrayList<ChangeInfo> sortedTargets,
2351             @NonNull SurfaceControl.Transaction startT) {
2352         // There needs to be a root on each display.
2353         for (int i = 0; i < sortedTargets.size(); ++i) {
2354             final WindowContainer<?> wc = sortedTargets.get(i).mContainer;
2355             // Don't include wallpapers since they are in a different DA.
2356             if (isWallpaper(wc)) continue;
2357             final DisplayContent dc = wc.getDisplayContent();
2358             if (dc == null) continue;
2359             final int endDisplayId = dc.getDisplayId();
2360 
2361             // Check if Root was already created for this display with a higher-Z window
2362             if (outInfo.findRootIndex(endDisplayId) >= 0) continue;
2363 
2364             WindowContainer<?> ancestor = findCommonAncestor(sortedTargets, wc);
2365 
2366             // Make leash based on highest (z-order) direct child of ancestor with a participant.
2367             // Check whether the ancestor is belonged to last parent, shouldn't happen.
2368             final boolean hasReparent = !wc.isDescendantOf(ancestor);
2369             WindowContainer leashReference = wc;
2370             if (hasReparent) {
2371                 Slog.e(TAG, "Did not find common ancestor! Ancestor= " + ancestor
2372                         + " target= " + wc);
2373             } else {
2374                 while (leashReference.getParent() != ancestor) {
2375                     leashReference = leashReference.getParent();
2376                 }
2377             }
2378             final SurfaceControl rootLeash = leashReference.makeAnimationLeash().setName(
2379                     "Transition Root: " + leashReference.getName()).build();
2380             rootLeash.setUnreleasedWarningCallSite("Transition.calculateTransitionRoots");
2381             // Update layers to start transaction because we prevent assignment during collect, so
2382             // the layer of transition root can be correct.
2383             updateDisplayLayers(dc, startT);
2384             startT.setLayer(rootLeash, leashReference.getLastLayer());
2385             outInfo.addRootLeash(endDisplayId, rootLeash,
2386                     ancestor.getBounds().left, ancestor.getBounds().top);
2387         }
2388     }
2389 
2390     /**
2391      * Construct a TransitionInfo object from a set of targets and changes. Also populates the
2392      * root surface.
2393      * @param sortedTargets The targets sorted by z-order from top (index 0) to bottom.
2394      * @param startT The start transaction - used to set-up new leashes.
2395      */
2396     @VisibleForTesting
2397     @NonNull
calculateTransitionInfo(@ransitionType int type, int flags, ArrayList<ChangeInfo> sortedTargets, @NonNull SurfaceControl.Transaction startT)2398     static TransitionInfo calculateTransitionInfo(@TransitionType int type, int flags,
2399             ArrayList<ChangeInfo> sortedTargets,
2400             @NonNull SurfaceControl.Transaction startT) {
2401         final TransitionInfo out = new TransitionInfo(type, flags);
2402         calculateTransitionRoots(out, sortedTargets, startT);
2403         if (out.getRootCount() == 0) {
2404             return out;
2405         }
2406 
2407         // Convert all the resolved ChangeInfos into TransactionInfo.Change objects in order.
2408         final int count = sortedTargets.size();
2409         for (int i = 0; i < count; ++i) {
2410             final ChangeInfo info = sortedTargets.get(i);
2411             final WindowContainer target = info.mContainer;
2412             final TransitionInfo.Change change = new TransitionInfo.Change(
2413                     target.mRemoteToken != null ? target.mRemoteToken.toWindowContainerToken()
2414                             : null, getLeashSurface(target, startT));
2415             // TODO(shell-transitions): Use leash for non-organized windows.
2416             if (info.mEndParent != null) {
2417                 change.setParent(info.mEndParent.mRemoteToken.toWindowContainerToken());
2418             }
2419             if (info.mStartParent != null && info.mStartParent.mRemoteToken != null
2420                     && target.getParent() != info.mStartParent) {
2421                 change.setLastParent(info.mStartParent.mRemoteToken.toWindowContainerToken());
2422             }
2423             change.setMode(info.getTransitMode(target));
2424             info.mReadyMode = change.getMode();
2425             change.setStartAbsBounds(info.mAbsoluteBounds);
2426             change.setFlags(info.getChangeFlags(target));
2427             info.mReadyFlags = change.getFlags();
2428             change.setDisplayId(info.mDisplayId, getDisplayId(target));
2429 
2430             final Task task = target.asTask();
2431             final TaskFragment taskFragment = target.asTaskFragment();
2432             final ActivityRecord activityRecord = target.asActivityRecord();
2433 
2434             if (task != null) {
2435                 final ActivityManager.RunningTaskInfo tinfo = new ActivityManager.RunningTaskInfo();
2436                 task.fillTaskInfo(tinfo);
2437                 change.setTaskInfo(tinfo);
2438                 change.setRotationAnimation(getTaskRotationAnimation(task));
2439                 final ActivityRecord topRunningActivity = task.topRunningActivity();
2440                 if (topRunningActivity != null) {
2441                     if (topRunningActivity.info.supportsPictureInPicture()) {
2442                         change.setAllowEnterPip(
2443                                 topRunningActivity.checkEnterPictureInPictureAppOpsState());
2444                     }
2445                     setEndFixedRotationIfNeeded(change, task, topRunningActivity);
2446                 }
2447             } else if ((info.mFlags & ChangeInfo.FLAG_SEAMLESS_ROTATION) != 0) {
2448                 change.setRotationAnimation(ROTATION_ANIMATION_SEAMLESS);
2449             }
2450 
2451             final WindowContainer<?> parent = target.getParent();
2452             final Rect bounds = target.getBounds();
2453             final Rect parentBounds = parent.getBounds();
2454             change.setEndRelOffset(bounds.left - parentBounds.left,
2455                     bounds.top - parentBounds.top);
2456             int endRotation = target.getWindowConfiguration().getRotation();
2457             if (activityRecord != null) {
2458                 // TODO(b/227427984): Shell needs to aware letterbox.
2459                 // Always use parent bounds of activity because letterbox area (e.g. fixed aspect
2460                 // ratio or size compat mode) should be included in the animation.
2461                 change.setEndAbsBounds(parentBounds);
2462                 if (activityRecord.getRelativeDisplayRotation() != 0
2463                         && !activityRecord.mTransitionController.useShellTransitionsRotation()) {
2464                     // Use parent rotation because shell doesn't know the surface is rotated.
2465                     endRotation = parent.getWindowConfiguration().getRotation();
2466                 }
2467             } else {
2468                 change.setEndAbsBounds(bounds);
2469             }
2470 
2471             if (activityRecord != null || (taskFragment != null && taskFragment.isEmbedded())) {
2472                 final int backgroundColor;
2473                 final TaskFragment organizedTf = activityRecord != null
2474                         ? activityRecord.getOrganizedTaskFragment()
2475                         : taskFragment.getOrganizedTaskFragment();
2476                 if (organizedTf != null && organizedTf.getAnimationParams()
2477                         .getAnimationBackgroundColor() != DEFAULT_ANIMATION_BACKGROUND_COLOR) {
2478                     // This window is embedded and has an animation background color set on the
2479                     // TaskFragment. Pass this color with this window, so the handler can use it as
2480                     // the animation background color if needed,
2481                     backgroundColor = organizedTf.getAnimationParams()
2482                             .getAnimationBackgroundColor();
2483                 } else {
2484                     // Set background color to Task theme color for activity and embedded
2485                     // TaskFragment in case we want to show background during the animation.
2486                     final Task parentTask = activityRecord != null
2487                             ? activityRecord.getTask()
2488                             : taskFragment.getTask();
2489                     backgroundColor = parentTask.getTaskDescription().getBackgroundColor();
2490                 }
2491                 // Set to opaque for animation background to prevent it from exposing the blank
2492                 // background or content below.
2493                 change.setBackgroundColor(ColorUtils.setAlphaComponent(backgroundColor, 255));
2494             }
2495 
2496             change.setRotation(info.mRotation, endRotation);
2497             if (info.mSnapshot != null) {
2498                 change.setSnapshot(info.mSnapshot, info.mSnapshotLuma);
2499             }
2500 
2501             out.addChange(change);
2502         }
2503 
2504         TransitionInfo.AnimationOptions animOptions = null;
2505 
2506         // Check if the top-most app is an activity (ie. activity->activity). If so, make sure to
2507         // honor its custom transition options.
2508         WindowContainer<?> topApp = null;
2509         for (int i = 0; i < sortedTargets.size(); i++) {
2510             if (isWallpaper(sortedTargets.get(i).mContainer)) continue;
2511             topApp = sortedTargets.get(i).mContainer;
2512             break;
2513         }
2514         if (topApp.asActivityRecord() != null) {
2515             final ActivityRecord topActivity = topApp.asActivityRecord();
2516             animOptions = addCustomActivityTransition(topActivity, true/* open */, null);
2517             animOptions = addCustomActivityTransition(topActivity, false/* open */, animOptions);
2518         }
2519         final WindowManager.LayoutParams animLp =
2520                 getLayoutParamsForAnimationsStyle(type, sortedTargets);
2521         if (animLp != null && animLp.type != TYPE_APPLICATION_STARTING
2522                 && animLp.windowAnimations != 0) {
2523             // Don't send animation options if no windowAnimations have been set or if the we are
2524             // running an app starting animation, in which case we don't want the app to be able to
2525             // change its animation directly.
2526             if (animOptions != null) {
2527                 animOptions.addOptionsFromLayoutParameters(animLp);
2528             } else {
2529                 animOptions = TransitionInfo.AnimationOptions
2530                         .makeAnimOptionsFromLayoutParameters(animLp);
2531             }
2532         }
2533         if (animOptions != null) {
2534             out.setAnimationOptions(animOptions);
2535         }
2536         return out;
2537     }
2538 
addCustomActivityTransition(ActivityRecord topActivity, boolean open, TransitionInfo.AnimationOptions animOptions)2539     static TransitionInfo.AnimationOptions addCustomActivityTransition(ActivityRecord topActivity,
2540             boolean open, TransitionInfo.AnimationOptions animOptions) {
2541         final ActivityRecord.CustomAppTransition customAnim =
2542                 topActivity.getCustomAnimation(open);
2543         if (customAnim != null) {
2544             if (animOptions == null) {
2545                 animOptions = TransitionInfo.AnimationOptions
2546                         .makeCommonAnimOptions(topActivity.packageName);
2547             }
2548             animOptions.addCustomActivityTransition(open, customAnim.mEnterAnim,
2549                     customAnim.mExitAnim, customAnim.mBackgroundColor);
2550         }
2551         return animOptions;
2552     }
2553 
setEndFixedRotationIfNeeded(@onNull TransitionInfo.Change change, @NonNull Task task, @NonNull ActivityRecord taskTopRunning)2554     private static void setEndFixedRotationIfNeeded(@NonNull TransitionInfo.Change change,
2555             @NonNull Task task, @NonNull ActivityRecord taskTopRunning) {
2556         if (!taskTopRunning.isVisibleRequested()) {
2557             // Fixed rotation only applies to opening or changing activity.
2558             return;
2559         }
2560         if (task.inMultiWindowMode() && taskTopRunning.inMultiWindowMode()) {
2561             // Display won't be rotated for multi window Task, so the fixed rotation won't be
2562             // applied. This can happen when the windowing mode is changed before the previous
2563             // fixed rotation is applied. Check both task and activity because the activity keeps
2564             // fullscreen mode when the task is entering PiP.
2565             return;
2566         }
2567         final int taskRotation = task.getWindowConfiguration().getDisplayRotation();
2568         final int activityRotation = taskTopRunning.getWindowConfiguration()
2569                 .getDisplayRotation();
2570         // If the Activity uses fixed rotation, its rotation will be applied to display after
2571         // the current transition is done, while the Task is still in the previous rotation.
2572         if (taskRotation != activityRotation) {
2573             change.setEndFixedRotation(activityRotation);
2574             return;
2575         }
2576 
2577         // For example, the task is entering PiP so it no longer decides orientation. If the next
2578         // orientation source (it could be an activity which was behind the PiP or launching to top)
2579         // will change display rotation, then set the fixed rotation hint as well so the animation
2580         // can consider the rotated position.
2581         if (!task.inPinnedWindowingMode() || taskTopRunning.mDisplayContent.inTransition()) {
2582             return;
2583         }
2584         final WindowContainer<?> orientationSource =
2585                 taskTopRunning.mDisplayContent.getLastOrientationSource();
2586         if (orientationSource == null) {
2587             return;
2588         }
2589         final int nextRotation = orientationSource.getWindowConfiguration().getDisplayRotation();
2590         if (taskRotation != nextRotation) {
2591             change.setEndFixedRotation(nextRotation);
2592         }
2593     }
2594 
2595     /**
2596      * Finds the top-most common ancestor of app targets.
2597      *
2598      * Makes sure that the previous parent is also a descendant to make sure the animation won't
2599      * be covered by other windows below the previous parent. For example, when reparenting an
2600      * activity from PiP Task to split screen Task.
2601      */
2602     @NonNull
findCommonAncestor( @onNull ArrayList<ChangeInfo> targets, @NonNull WindowContainer<?> topApp)2603     private static WindowContainer<?> findCommonAncestor(
2604             @NonNull ArrayList<ChangeInfo> targets,
2605             @NonNull WindowContainer<?> topApp) {
2606         final int displayId = getDisplayId(topApp);
2607         WindowContainer<?> ancestor = topApp.getParent();
2608         // Go up ancestor parent chain until all targets are descendants. Ancestor should never be
2609         // null because all targets are attached.
2610         for (int i = targets.size() - 1; i >= 0; i--) {
2611             final ChangeInfo change = targets.get(i);
2612             final WindowContainer wc = change.mContainer;
2613             if (isWallpaper(wc) || getDisplayId(wc) != displayId) {
2614                 // Skip the non-app window or windows on a different display
2615                 continue;
2616             }
2617             // Re-initiate the last parent as the initial ancestor instead of the top target.
2618             // When move a leaf task from organized task to display area, try to keep the transition
2619             // root be the original organized task for close transition animation.
2620             // Otherwise, shell will use wrong root layer to play animation.
2621             // Note: Since the target is sorted, so only need to do this at the lowest target.
2622             if (change.mStartParent != null && wc.getParent() != null
2623                     && change.mStartParent.isAttached() && wc.getParent() != change.mStartParent
2624                     && i == targets.size() - 1) {
2625                 final int transitionMode = change.getTransitMode(wc);
2626                 if (transitionMode == TRANSIT_CLOSE || transitionMode == TRANSIT_TO_BACK) {
2627                     ancestor = change.mStartParent;
2628                     continue;
2629                 }
2630             }
2631             while (!wc.isDescendantOf(ancestor)) {
2632                 ancestor = ancestor.getParent();
2633             }
2634 
2635             // Make sure the previous parent is also a descendant to make sure the animation won't
2636             // be covered by other windows below the previous parent. For example, when reparenting
2637             // an activity from PiP Task to split screen Task.
2638             final WindowContainer prevParent = change.mCommonAncestor;
2639             if (prevParent == null || !prevParent.isAttached()) {
2640                 continue;
2641             }
2642             while (prevParent != ancestor && !prevParent.isDescendantOf(ancestor)) {
2643                 ancestor = ancestor.getParent();
2644             }
2645         }
2646         return ancestor;
2647     }
2648 
getLayoutParamsForAnimationsStyle(int type, ArrayList<ChangeInfo> sortedTargets)2649     private static WindowManager.LayoutParams getLayoutParamsForAnimationsStyle(int type,
2650             ArrayList<ChangeInfo> sortedTargets) {
2651         // Find the layout params of the top-most application window that is part of the
2652         // transition, which is what will control the animation theme.
2653         final ArraySet<Integer> activityTypes = new ArraySet<>();
2654         final int targetCount = sortedTargets.size();
2655         for (int i = 0; i < targetCount; ++i) {
2656             final WindowContainer target = sortedTargets.get(i).mContainer;
2657             if (target.asActivityRecord() != null) {
2658                 activityTypes.add(target.getActivityType());
2659             } else if (target.asWindowToken() == null && target.asWindowState() == null) {
2660                 // We don't want app to customize animations that are not activity to activity.
2661                 // Activity-level transitions can only include activities, wallpaper and subwindows.
2662                 // Anything else is not a WindowToken nor a WindowState and is "higher" in the
2663                 // hierarchy which means we are no longer in an activity transition.
2664                 return null;
2665             }
2666         }
2667         if (activityTypes.isEmpty()) {
2668             // We don't want app to be able to customize transitions that are not activity to
2669             // activity through the layout parameter animation style.
2670             return null;
2671         }
2672         final ActivityRecord animLpActivity =
2673                 findAnimLayoutParamsActivityRecord(sortedTargets, type, activityTypes);
2674         final WindowState mainWindow = animLpActivity != null
2675                 ? animLpActivity.findMainWindow() : null;
2676         return mainWindow != null ? mainWindow.mAttrs : null;
2677     }
2678 
findAnimLayoutParamsActivityRecord( List<ChangeInfo> sortedTargets, @TransitionType int transit, ArraySet<Integer> activityTypes)2679     private static ActivityRecord findAnimLayoutParamsActivityRecord(
2680             List<ChangeInfo> sortedTargets,
2681             @TransitionType int transit, ArraySet<Integer> activityTypes) {
2682         // Remote animations always win, but fullscreen windows override non-fullscreen windows.
2683         ActivityRecord result = lookForTopWindowWithFilter(sortedTargets,
2684                 w -> w.getRemoteAnimationDefinition() != null
2685                     && w.getRemoteAnimationDefinition().hasTransition(transit, activityTypes));
2686         if (result != null) {
2687             return result;
2688         }
2689         result = lookForTopWindowWithFilter(sortedTargets,
2690                 w -> w.fillsParent() && w.findMainWindow() != null);
2691         if (result != null) {
2692             return result;
2693         }
2694         return lookForTopWindowWithFilter(sortedTargets, w -> w.findMainWindow() != null);
2695     }
2696 
lookForTopWindowWithFilter(List<ChangeInfo> sortedTargets, Predicate<ActivityRecord> filter)2697     private static ActivityRecord lookForTopWindowWithFilter(List<ChangeInfo> sortedTargets,
2698             Predicate<ActivityRecord> filter) {
2699         final int count = sortedTargets.size();
2700         for (int i = 0; i < count; ++i) {
2701             final WindowContainer target = sortedTargets.get(i).mContainer;
2702             final ActivityRecord activityRecord = target.asTaskFragment() != null
2703                     ? target.asTaskFragment().getTopNonFinishingActivity()
2704                     : target.asActivityRecord();
2705             if (activityRecord != null && filter.test(activityRecord)) {
2706                 return activityRecord;
2707             }
2708         }
2709         return null;
2710     }
2711 
getTaskRotationAnimation(@onNull Task task)2712     private static int getTaskRotationAnimation(@NonNull Task task) {
2713         final ActivityRecord top = task.getTopVisibleActivity();
2714         if (top == null) return ROTATION_ANIMATION_UNSPECIFIED;
2715         final WindowState mainWin = top.findMainWindow(false);
2716         if (mainWin == null) return ROTATION_ANIMATION_UNSPECIFIED;
2717         int anim = mainWin.getRotationAnimationHint();
2718         if (anim >= 0) return anim;
2719         anim = mainWin.getAttrs().rotationAnimation;
2720         if (anim != ROTATION_ANIMATION_SEAMLESS) return anim;
2721         if (mainWin != task.mDisplayContent.getDisplayPolicy().getTopFullscreenOpaqueWindow()
2722                 || !top.matchParentBounds()) {
2723             // At the moment, we only support seamless rotation if there is only one window showing.
2724             return ROTATION_ANIMATION_UNSPECIFIED;
2725         }
2726         return mainWin.getAttrs().rotationAnimation;
2727     }
2728 
validateKeyguardOcclusion()2729     private void validateKeyguardOcclusion() {
2730         if ((mFlags & KEYGUARD_VISIBILITY_TRANSIT_FLAGS) != 0) {
2731             mController.mStateValidators.add(
2732                 mController.mAtm.mWindowManager.mPolicy::applyKeyguardOcclusionChange);
2733         }
2734     }
2735 
2736     /**
2737      * Returns {@code true} if the transition and the corresponding transaction should be applied
2738      * on display thread. Currently, this only checks for display rotation change because the order
2739      * of dispatching the new display info will be after requesting the windows to sync drawing.
2740      * That avoids potential flickering of screen overlays (e.g. cutout, rounded corner). Also,
2741      * because the display thread has a higher priority, it is faster to perform the configuration
2742      * changes and window hierarchy traversal.
2743      */
shouldApplyOnDisplayThread()2744     boolean shouldApplyOnDisplayThread() {
2745         for (int i = mParticipants.size() - 1; i >= 0; --i) {
2746             final DisplayContent dc = mParticipants.valueAt(i).asDisplayContent();
2747             if (dc == null) continue;
2748             final ChangeInfo changeInfo = mChanges.get(dc);
2749             if (changeInfo != null && changeInfo.mRotation != dc.getRotation()) {
2750                 return Looper.myLooper() != mController.mAtm.mWindowManager.mH.getLooper();
2751             }
2752         }
2753         return false;
2754     }
2755 
2756     /** Applies the new configuration for the changed displays. */
applyDisplayChangeIfNeeded()2757     void applyDisplayChangeIfNeeded() {
2758         for (int i = mParticipants.size() - 1; i >= 0; --i) {
2759             final WindowContainer<?> wc = mParticipants.valueAt(i);
2760             final DisplayContent dc = wc.asDisplayContent();
2761             if (dc == null || !mChanges.get(dc).hasChanged()) continue;
2762             dc.sendNewConfiguration();
2763             // Set to ready if no other change controls the ready state. But if there is, such as
2764             // if an activity is pausing, it will call setReady(ar, false) and wait for the next
2765             // resumed activity. Then do not set to ready because the transition only contains
2766             // partial participants. Otherwise the transition may only handle HIDE and miss OPEN.
2767             if (!mReadyTracker.mUsed) {
2768                 setReady(dc, true);
2769             }
2770         }
2771     }
2772 
getLegacyIsReady()2773     boolean getLegacyIsReady() {
2774         return isCollecting() && mSyncId >= 0;
2775     }
2776 
asyncTraceBegin(@onNull String name, int cookie)2777     static void asyncTraceBegin(@NonNull String name, int cookie) {
2778         Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_WINDOW_MANAGER, TAG, name, cookie);
2779     }
2780 
asyncTraceEnd(int cookie)2781     static void asyncTraceEnd(int cookie) {
2782         Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_WINDOW_MANAGER, TAG, cookie);
2783     }
2784 
2785     @VisibleForTesting
2786     static class ChangeInfo {
2787         private static final int FLAG_NONE = 0;
2788 
2789         /**
2790          * When set, the associated WindowContainer has been explicitly requested to be a
2791          * seamless rotation. This is currently only used by DisplayContent during fixed-rotation.
2792          */
2793         private static final int FLAG_SEAMLESS_ROTATION = 1;
2794         private static final int FLAG_TRANSIENT_LAUNCH = 2;
2795         private static final int FLAG_ABOVE_TRANSIENT_LAUNCH = 4;
2796 
2797         /** This container explicitly requested no-animation (usually Activity level). */
2798         private static final int FLAG_CHANGE_NO_ANIMATION = 0x8;
2799         /**
2800          * This container has at-least one child which IS animating (not marked NO_ANIMATION).
2801          * Used during promotion. This trumps `FLAG_NO_ANIMATION` (if both are set).
2802          */
2803         private static final int FLAG_CHANGE_YES_ANIMATION = 0x10;
2804 
2805         /** Whether this change's container moved to the top. */
2806         private static final int FLAG_CHANGE_MOVED_TO_TOP = 0x20;
2807 
2808         @IntDef(prefix = { "FLAG_" }, value = {
2809                 FLAG_NONE,
2810                 FLAG_SEAMLESS_ROTATION,
2811                 FLAG_TRANSIENT_LAUNCH,
2812                 FLAG_ABOVE_TRANSIENT_LAUNCH,
2813                 FLAG_CHANGE_NO_ANIMATION,
2814                 FLAG_CHANGE_YES_ANIMATION,
2815                 FLAG_CHANGE_MOVED_TO_TOP
2816         })
2817         @Retention(RetentionPolicy.SOURCE)
2818         @interface Flag {}
2819 
2820         @NonNull final WindowContainer mContainer;
2821         /**
2822          * "Parent" that is also included in the transition. When populating the parent changes, we
2823          * may skip the intermediate parents, so this may not be the actual parent in the hierarchy.
2824          */
2825         WindowContainer mEndParent;
2826         /** Actual parent window before change state. */
2827         WindowContainer mStartParent;
2828         /**
2829          * When the window is reparented during the transition, this is the common ancestor window
2830          * of the {@link #mStartParent} and the current parent. This is needed because the
2831          * {@link #mStartParent} may have been detached when the transition starts.
2832          */
2833         WindowContainer mCommonAncestor;
2834 
2835         // State tracking
2836         boolean mExistenceChanged = false;
2837         // before change state
2838         boolean mVisible;
2839         int mWindowingMode;
2840         final Rect mAbsoluteBounds = new Rect();
2841         boolean mShowWallpaper;
2842         int mRotation = ROTATION_UNDEFINED;
2843         int mDisplayId = -1;
2844         @ActivityInfo.Config int mKnownConfigChanges;
2845 
2846         /** Extra information about this change. */
2847         @Flag int mFlags = FLAG_NONE;
2848 
2849         /** Snapshot surface and luma, if relevant. */
2850         SurfaceControl mSnapshot;
2851         float mSnapshotLuma;
2852 
2853         /** The mode which is set when the transition is ready. */
2854         @TransitionInfo.TransitionMode
2855         int mReadyMode;
2856 
2857         /** The flags which is set when the transition is ready. */
2858         @TransitionInfo.ChangeFlags
2859         int mReadyFlags;
2860 
ChangeInfo(@onNull WindowContainer origState)2861         ChangeInfo(@NonNull WindowContainer origState) {
2862             mContainer = origState;
2863             mVisible = origState.isVisibleRequested();
2864             mWindowingMode = origState.getWindowingMode();
2865             mAbsoluteBounds.set(origState.getBounds());
2866             mShowWallpaper = origState.showWallpaper();
2867             mRotation = origState.getWindowConfiguration().getRotation();
2868             mStartParent = origState.getParent();
2869             mDisplayId = getDisplayId(origState);
2870         }
2871 
2872         @VisibleForTesting
ChangeInfo(@onNull WindowContainer container, boolean visible, boolean existChange)2873         ChangeInfo(@NonNull WindowContainer container, boolean visible, boolean existChange) {
2874             this(container);
2875             mVisible = visible;
2876             mExistenceChanged = existChange;
2877             mShowWallpaper = false;
2878         }
2879 
2880         @Override
toString()2881         public String toString() {
2882             return mContainer.toString();
2883         }
2884 
hasChanged()2885         boolean hasChanged() {
2886             // the task including transient launch must promote to root task
2887             if ((mFlags & ChangeInfo.FLAG_TRANSIENT_LAUNCH) != 0
2888                     || (mFlags & ChangeInfo.FLAG_ABOVE_TRANSIENT_LAUNCH) != 0) {
2889                 return true;
2890             }
2891             // If it's invisible and hasn't changed visibility, always return false since even if
2892             // something changed, it wouldn't be a visible change.
2893             final boolean currVisible = mContainer.isVisibleRequested();
2894             if (currVisible == mVisible && !mVisible) return false;
2895             return currVisible != mVisible
2896                     || mKnownConfigChanges != 0
2897                     // if mWindowingMode is 0, this container wasn't attached at collect time, so
2898                     // assume no change in windowing-mode.
2899                     || (mWindowingMode != 0 && mContainer.getWindowingMode() != mWindowingMode)
2900                     || !mContainer.getBounds().equals(mAbsoluteBounds)
2901                     || mRotation != mContainer.getWindowConfiguration().getRotation()
2902                     || mDisplayId != getDisplayId(mContainer)
2903                     || (mFlags & ChangeInfo.FLAG_CHANGE_MOVED_TO_TOP) != 0;
2904         }
2905 
2906         @TransitionInfo.TransitionMode
getTransitMode(@onNull WindowContainer wc)2907         int getTransitMode(@NonNull WindowContainer wc) {
2908             if ((mFlags & ChangeInfo.FLAG_ABOVE_TRANSIENT_LAUNCH) != 0) {
2909                 return mExistenceChanged ? TRANSIT_CLOSE : TRANSIT_TO_BACK;
2910             }
2911             final boolean nowVisible = wc.isVisibleRequested();
2912             if (nowVisible == mVisible) {
2913                 return TRANSIT_CHANGE;
2914             }
2915             if (mExistenceChanged) {
2916                 return nowVisible ? TRANSIT_OPEN : TRANSIT_CLOSE;
2917             } else {
2918                 return nowVisible ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK;
2919             }
2920         }
2921 
2922         @TransitionInfo.ChangeFlags
getChangeFlags(@onNull WindowContainer wc)2923         int getChangeFlags(@NonNull WindowContainer wc) {
2924             int flags = 0;
2925             if (mShowWallpaper || wc.showWallpaper()) {
2926                 flags |= FLAG_SHOW_WALLPAPER;
2927             }
2928             if (isTranslucent(wc)) {
2929                 flags |= FLAG_TRANSLUCENT;
2930             }
2931             if (wc.mWmService.mAtmService.mBackNavigationController.isMonitorTransitionTarget(wc)) {
2932                 flags |= TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
2933             }
2934             final Task task = wc.asTask();
2935             if (task != null) {
2936                 final ActivityRecord topActivity = task.getTopNonFinishingActivity();
2937                 if (topActivity != null) {
2938                     if (topActivity.mStartingData != null
2939                             && topActivity.mStartingData.hasImeSurface()) {
2940                         flags |= FLAG_WILL_IME_SHOWN;
2941                     }
2942                     if (topActivity.mLaunchTaskBehind) {
2943                         Slog.e(TAG, "Unexpected launch-task-behind operation in shell transition");
2944                         flags |= FLAG_TASK_LAUNCHING_BEHIND;
2945                     }
2946                 }
2947                 if (task.voiceSession != null) {
2948                     flags |= FLAG_IS_VOICE_INTERACTION;
2949                 }
2950             }
2951             Task parentTask = null;
2952             final ActivityRecord record = wc.asActivityRecord();
2953             if (record != null) {
2954                 parentTask = record.getTask();
2955                 if (record.mVoiceInteraction) {
2956                     flags |= FLAG_IS_VOICE_INTERACTION;
2957                 }
2958                 flags |= record.mTransitionChangeFlags;
2959             }
2960             final TaskFragment taskFragment = wc.asTaskFragment();
2961             if (taskFragment != null && task == null) {
2962                 parentTask = taskFragment.getTask();
2963             }
2964             if (parentTask != null) {
2965                 if (parentTask.forAllLeafTaskFragments(TaskFragment::isEmbedded)) {
2966                     // Whether this is in a Task with embedded activity.
2967                     flags |= FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
2968                 }
2969                 if (parentTask.forAllActivities(ActivityRecord::hasStartingWindow)) {
2970                     // The starting window should cover all windows inside the leaf Task.
2971                     flags |= FLAG_IS_BEHIND_STARTING_WINDOW;
2972                 }
2973                 if (isWindowFillingTask(wc, parentTask)) {
2974                     // Whether the container fills its parent Task bounds.
2975                     flags |= FLAG_FILLS_TASK;
2976                 }
2977             } else {
2978                 final DisplayContent dc = wc.asDisplayContent();
2979                 if (dc != null) {
2980                     flags |= FLAG_IS_DISPLAY;
2981                     if (dc.hasAlertWindowSurfaces()) {
2982                         flags |= FLAG_DISPLAY_HAS_ALERT_WINDOWS;
2983                     }
2984                 } else if (isWallpaper(wc)) {
2985                     flags |= FLAG_IS_WALLPAPER;
2986                 } else if (isInputMethod(wc)) {
2987                     flags |= FLAG_IS_INPUT_METHOD;
2988                 } else {
2989                     // In this condition, the wc can only be WindowToken or DisplayArea.
2990                     final int type = wc.getWindowType();
2991                     if (type >= WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW
2992                             && type <= WindowManager.LayoutParams.LAST_SYSTEM_WINDOW) {
2993                         flags |= TransitionInfo.FLAG_IS_SYSTEM_WINDOW;
2994                     }
2995                 }
2996             }
2997             if ((mFlags & FLAG_CHANGE_NO_ANIMATION) != 0
2998                     && (mFlags & FLAG_CHANGE_YES_ANIMATION) == 0) {
2999                 flags |= FLAG_NO_ANIMATION;
3000             }
3001             if ((mFlags & FLAG_CHANGE_MOVED_TO_TOP) != 0) {
3002                 flags |= FLAG_MOVED_TO_TOP;
3003             }
3004             return flags;
3005         }
3006 
3007         /** Whether the container fills its parent Task bounds before and after the transition. */
isWindowFillingTask(@onNull WindowContainer wc, @NonNull Task parentTask)3008         private boolean isWindowFillingTask(@NonNull WindowContainer wc, @NonNull Task parentTask) {
3009             final Rect taskBounds = parentTask.getBounds();
3010             final int taskWidth = taskBounds.width();
3011             final int taskHeight = taskBounds.height();
3012             final Rect startBounds = mAbsoluteBounds;
3013             final Rect endBounds = wc.getBounds();
3014             // Treat it as filling the task if it is not visible.
3015             final boolean isInvisibleOrFillingTaskBeforeTransition = !mVisible
3016                     || (taskWidth == startBounds.width() && taskHeight == startBounds.height());
3017             final boolean isInVisibleOrFillingTaskAfterTransition = !wc.isVisibleRequested()
3018                     || (taskWidth == endBounds.width() && taskHeight == endBounds.height());
3019             return isInvisibleOrFillingTaskBeforeTransition
3020                     && isInVisibleOrFillingTaskAfterTransition;
3021         }
3022     }
3023 
3024     /**
3025      * This transition will be considered not-ready until a corresponding call to
3026      * {@link #continueTransitionReady}
3027      */
deferTransitionReady()3028     void deferTransitionReady() {
3029         ++mReadyTracker.mDeferReadyDepth;
3030         // Make sure it wait until #continueTransitionReady() is called.
3031         mSyncEngine.setReady(mSyncId, false);
3032     }
3033 
3034     /** This undoes one call to {@link #deferTransitionReady}. */
continueTransitionReady()3035     void continueTransitionReady() {
3036         --mReadyTracker.mDeferReadyDepth;
3037         // Apply ready in case it is waiting for the previous defer call.
3038         applyReady();
3039     }
3040 
3041     /**
3042      * The transition sync mechanism has 2 parts:
3043      *   1. Whether all WM operations for a particular transition are "ready" (eg. did the app
3044      *      launch or stop or get a new configuration?).
3045      *   2. Whether all the windows involved have finished drawing their final-state content.
3046      *
3047      * A transition animation can play once both parts are complete. This ready-tracker keeps track
3048      * of part (1). Currently, WM code assumes that "readiness" (part 1) is grouped. This means that
3049      * even if the WM operations in one group are ready, the whole transition itself may not be
3050      * ready if there are WM operations still pending in another group. This class helps keep track
3051      * of readiness across the multiple groups. Currently, we assume that each display is a group
3052      * since that is how it has been until now.
3053      */
3054     private static class ReadyTracker {
3055         private final ArrayMap<WindowContainer, Boolean> mReadyGroups = new ArrayMap<>();
3056 
3057         /**
3058          * Ensures that this doesn't report as allReady before it has been used. This is needed
3059          * in very niche cases where a transition is a no-op (nothing has been collected) but we
3060          * still want to be marked ready (via. setAllReady).
3061          */
3062         private boolean mUsed = false;
3063 
3064         /**
3065          * If true, this overrides all ready groups and reports ready. Used by shell-initiated
3066          * transitions via {@link #setAllReady()}.
3067          */
3068         private boolean mReadyOverride = false;
3069 
3070         /**
3071          * When non-zero, this transition is forced not-ready (even over setAllReady()). Use this
3072          * (via deferTransitionReady/continueTransitionReady) for situations where we want to do
3073          * bulk operations which could trigger surface-placement but the existing ready-state
3074          * isn't known.
3075          */
3076         private int mDeferReadyDepth = 0;
3077 
3078         /**
3079          * Adds a ready-group. Any setReady calls in this subtree will be tracked together. For
3080          * now these are only DisplayContents.
3081          */
addGroup(WindowContainer wc)3082         void addGroup(WindowContainer wc) {
3083             if (mReadyGroups.containsKey(wc)) {
3084                 Slog.e(TAG, "Trying to add a ready-group twice: " + wc);
3085                 return;
3086             }
3087             mReadyGroups.put(wc, false);
3088         }
3089 
3090         /**
3091          * Sets a group's ready state.
3092          * @param wc Any container within a group's subtree. Used to identify the ready-group.
3093          */
setReadyFrom(WindowContainer wc, boolean ready)3094         void setReadyFrom(WindowContainer wc, boolean ready) {
3095             mUsed = true;
3096             WindowContainer current = wc;
3097             while (current != null) {
3098                 if (isReadyGroup(current)) {
3099                     mReadyGroups.put(current, ready);
3100                     ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Setting Ready-group to"
3101                             + " %b. group=%s from %s", ready, current, wc);
3102                     break;
3103                 }
3104                 current = current.getParent();
3105             }
3106         }
3107 
3108         /** Marks this as ready regardless of individual groups. */
setAllReady()3109         void setAllReady() {
3110             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Setting allReady override");
3111             mUsed = true;
3112             mReadyOverride = true;
3113         }
3114 
3115         /** @return true if all tracked subtrees are ready. */
allReady()3116         boolean allReady() {
3117             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " allReady query: used=%b "
3118                     + "override=%b defer=%d states=[%s]", mUsed, mReadyOverride, mDeferReadyDepth,
3119                     groupsToString());
3120             // If the readiness has never been touched, mUsed will be false. We never want to
3121             // consider a transition ready if nothing has been reported on it.
3122             if (!mUsed) return false;
3123             // If we are deferring readiness, we never report ready. This is usually temporary.
3124             if (mDeferReadyDepth > 0) return false;
3125             // Next check all the ready groups to see if they are ready. We can short-cut this if
3126             // ready-override is set (which is treated as "everything is marked ready").
3127             if (mReadyOverride) return true;
3128             for (int i = mReadyGroups.size() - 1; i >= 0; --i) {
3129                 final WindowContainer wc = mReadyGroups.keyAt(i);
3130                 if (!wc.isAttached() || !wc.isVisibleRequested()) continue;
3131                 if (!mReadyGroups.valueAt(i)) return false;
3132             }
3133             return true;
3134         }
3135 
groupsToString()3136         private String groupsToString() {
3137             StringBuilder b = new StringBuilder();
3138             for (int i = 0; i < mReadyGroups.size(); ++i) {
3139                 if (i != 0) b.append(',');
3140                 b.append(mReadyGroups.keyAt(i)).append(':')
3141                         .append(mReadyGroups.valueAt(i));
3142             }
3143             return b.toString();
3144         }
3145     }
3146 
3147     /**
3148      * The container to represent the depth relation for calculating transition targets. The window
3149      * container with larger depth is put at larger index. For the same depth, higher z-order has
3150      * larger index.
3151      */
3152     private static class Targets {
3153         /** All targets. Its keys (depth) are sorted in ascending order naturally. */
3154         final SparseArray<ChangeInfo> mArray = new SparseArray<>();
3155         /** The targets which were represented by their parent. */
3156         private ArrayList<ChangeInfo> mRemovedTargets;
3157         private int mDepthFactor;
3158 
add(ChangeInfo target)3159         void add(ChangeInfo target) {
3160             // The number of slots per depth is larger than the total number of window container,
3161             // so the depth score (key) won't have collision.
3162             if (mDepthFactor == 0) {
3163                 mDepthFactor = target.mContainer.mWmService.mRoot.getTreeWeight() + 1;
3164             }
3165             int score = target.mContainer.getPrefixOrderIndex();
3166             WindowContainer<?> wc = target.mContainer;
3167             while (wc != null) {
3168                 final WindowContainer<?> parent = wc.getParent();
3169                 if (parent != null) {
3170                     score += mDepthFactor;
3171                 }
3172                 wc = parent;
3173             }
3174             mArray.put(score, target);
3175         }
3176 
remove(int index)3177         void remove(int index) {
3178             final ChangeInfo removingTarget = mArray.valueAt(index);
3179             mArray.removeAt(index);
3180             if (mRemovedTargets == null) {
3181                 mRemovedTargets = new ArrayList<>();
3182             }
3183             mRemovedTargets.add(removingTarget);
3184         }
3185 
wasParticipated(ChangeInfo wc)3186         boolean wasParticipated(ChangeInfo wc) {
3187             return mArray.indexOfValue(wc) >= 0
3188                     || (mRemovedTargets != null && mRemovedTargets.contains(wc));
3189         }
3190 
3191         /** Returns the target list sorted by z-order in ascending order (index 0 is top). */
getListSortedByZ()3192         ArrayList<ChangeInfo> getListSortedByZ() {
3193             final SparseArray<ChangeInfo> arrayByZ = new SparseArray<>(mArray.size());
3194             for (int i = mArray.size() - 1; i >= 0; --i) {
3195                 final int zOrder = mArray.keyAt(i) % mDepthFactor;
3196                 arrayByZ.put(zOrder, mArray.valueAt(i));
3197             }
3198             final ArrayList<ChangeInfo> sortedTargets = new ArrayList<>(arrayByZ.size());
3199             for (int i = arrayByZ.size() - 1; i >= 0; --i) {
3200                 sortedTargets.add(arrayByZ.valueAt(i));
3201             }
3202             return sortedTargets;
3203         }
3204     }
3205 
3206     /**
3207      * Interface for freezing a container's content during sync preparation. Really just one impl
3208      * but broken into an interface for testing (since you can't take screenshots in unit tests).
3209      */
3210     interface IContainerFreezer {
3211         /**
3212          * Makes sure a particular window is "frozen" for the remainder of a sync.
3213          *
3214          * @return whether the freeze was successful. It fails if `wc` is already in a frozen window
3215          *         or is not visible/ready.
3216          */
freeze(@onNull WindowContainer wc, @NonNull Rect bounds)3217         boolean freeze(@NonNull WindowContainer wc, @NonNull Rect bounds);
3218 
3219         /** Populates `t` with operations that clean-up any state created to set-up the freeze. */
cleanUp(SurfaceControl.Transaction t)3220         void cleanUp(SurfaceControl.Transaction t);
3221     }
3222 
3223     /**
3224      * Freezes container content by taking a screenshot. Because screenshots are heavy, usage of
3225      * any container "freeze" is currently explicit. WM code needs to be prudent about which
3226      * containers to freeze.
3227      */
3228     @VisibleForTesting
3229     private class ScreenshotFreezer implements IContainerFreezer {
3230         /** Keeps track of which windows are frozen. Not all frozen windows have snapshots. */
3231         private final ArraySet<WindowContainer> mFrozen = new ArraySet<>();
3232 
3233         /** Takes a screenshot and puts it at the top of the container's surface. */
3234         @Override
freeze(@onNull WindowContainer wc, @NonNull Rect bounds)3235         public boolean freeze(@NonNull WindowContainer wc, @NonNull Rect bounds) {
3236             if (!wc.isVisibleRequested()) return false;
3237 
3238             // Check if any parents have already been "frozen". If so, `wc` is already part of that
3239             // snapshot, so just skip it.
3240             for (WindowContainer p = wc; p != null; p = p.getParent()) {
3241                 if (mFrozen.contains(p)) return false;
3242             }
3243 
3244             if (mIsSeamlessRotation) {
3245                 WindowState top = wc.getDisplayContent() == null ? null
3246                         : wc.getDisplayContent().getDisplayPolicy().getTopFullscreenOpaqueWindow();
3247                 if (top != null && (top == wc || top.isDescendantOf(wc))) {
3248                     // Don't use screenshots for seamless windows: these will use BLAST even if not
3249                     // BLAST mode.
3250                     mFrozen.add(wc);
3251                     return true;
3252                 }
3253             }
3254 
3255             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Screenshotting %s [%s]",
3256                     wc.toString(), bounds.toString());
3257 
3258             Rect cropBounds = new Rect(bounds);
3259             cropBounds.offsetTo(0, 0);
3260             final boolean isDisplayRotation = wc.asDisplayContent() != null
3261                     && wc.asDisplayContent().isRotationChanging();
3262             ScreenCapture.LayerCaptureArgs captureArgs =
3263                     new ScreenCapture.LayerCaptureArgs.Builder(wc.getSurfaceControl())
3264                             .setSourceCrop(cropBounds)
3265                             .setCaptureSecureLayers(true)
3266                             .setAllowProtected(true)
3267                             .setHintForSeamlessTransition(isDisplayRotation)
3268                             .build();
3269             ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer =
3270                     ScreenCapture.captureLayers(captureArgs);
3271             final HardwareBuffer buffer = screenshotBuffer == null ? null
3272                     : screenshotBuffer.getHardwareBuffer();
3273             if (buffer == null || buffer.getWidth() <= 1 || buffer.getHeight() <= 1) {
3274                 // This can happen when display is not ready.
3275                 Slog.w(TAG, "Failed to capture screenshot for " + wc);
3276                 return false;
3277             }
3278             // Some tests may check the name "RotationLayer" to detect display rotation.
3279             final String name = isDisplayRotation ? "RotationLayer" : "transition snapshot: " + wc;
3280             SurfaceControl snapshotSurface = wc.makeAnimationLeash()
3281                     .setName(name)
3282                     .setOpaque(wc.fillsParent())
3283                     .setParent(wc.getSurfaceControl())
3284                     .setSecure(screenshotBuffer.containsSecureLayers())
3285                     .setCallsite("Transition.ScreenshotSync")
3286                     .setBLASTLayer()
3287                     .build();
3288             mFrozen.add(wc);
3289             final ChangeInfo changeInfo = Objects.requireNonNull(mChanges.get(wc));
3290             changeInfo.mSnapshot = snapshotSurface;
3291             if (isDisplayRotation) {
3292                 // This isn't cheap, so only do it for display rotations.
3293                 changeInfo.mSnapshotLuma = TransitionAnimation.getBorderLuma(
3294                         buffer, screenshotBuffer.getColorSpace());
3295             }
3296             SurfaceControl.Transaction t = wc.mWmService.mTransactionFactory.get();
3297             TransitionAnimation.configureScreenshotLayer(t, snapshotSurface, screenshotBuffer);
3298             t.show(snapshotSurface);
3299 
3300             // Place it on top of anything else in the container.
3301             t.setLayer(snapshotSurface, Integer.MAX_VALUE);
3302             t.apply();
3303             t.close();
3304             buffer.close();
3305 
3306             // Detach the screenshot on the sync transaction (the screenshot is just meant to
3307             // freeze the window until the sync transaction is applied (with all its other
3308             // corresponding changes), so this is how we unfreeze it.
3309             wc.getSyncTransaction().reparent(snapshotSurface, null /* newParent */);
3310             return true;
3311         }
3312 
3313         @Override
cleanUp(SurfaceControl.Transaction t)3314         public void cleanUp(SurfaceControl.Transaction t) {
3315             for (int i = 0; i < mFrozen.size(); ++i) {
3316                 SurfaceControl snap =
3317                         Objects.requireNonNull(mChanges.get(mFrozen.valueAt(i))).mSnapshot;
3318                 // May be null if it was frozen via BLAST override.
3319                 if (snap == null) continue;
3320                 t.reparent(snap, null /* newParent */);
3321             }
3322         }
3323     }
3324 
3325     private static class Token extends Binder {
3326         final WeakReference<Transition> mTransition;
3327 
Token(Transition transition)3328         Token(Transition transition) {
3329             mTransition = new WeakReference<>(transition);
3330         }
3331 
3332         @Override
toString()3333         public String toString() {
3334             return "Token{" + Integer.toHexString(System.identityHashCode(this)) + " "
3335                     + mTransition.get() + "}";
3336         }
3337     }
3338 }
3339