• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.wm;
18 
19 import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS;
20 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
21 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
22 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
23 import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
24 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
25 import static android.view.Display.DEFAULT_DISPLAY;
26 import static android.view.Display.INVALID_DISPLAY;
27 import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION;
28 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
29 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
30 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
31 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
32 import static android.view.WindowManager.TRANSIT_CHANGE;
33 import static android.view.WindowManager.TRANSIT_CLOSE;
34 import static android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS;
35 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
36 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION;
37 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION;
38 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE;
39 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER;
40 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
41 import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
42 import static android.view.WindowManager.TRANSIT_OPEN;
43 import static android.view.WindowManager.TRANSIT_TO_BACK;
44 import static android.view.WindowManager.TRANSIT_TO_FRONT;
45 import static android.view.WindowManager.TransitionFlags;
46 import static android.view.WindowManager.TransitionType;
47 import static android.view.WindowManager.transitTypeToString;
48 import static android.window.TaskFragmentAnimationParams.DEFAULT_ANIMATION_BACKGROUND_COLOR;
49 import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS;
50 import static android.window.TransitionInfo.FLAG_FILLS_TASK;
51 import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
52 import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
53 import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
54 import static android.window.TransitionInfo.FLAG_IS_INPUT_METHOD;
55 import static android.window.TransitionInfo.FLAG_IS_VOICE_INTERACTION;
56 import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
57 import static android.window.TransitionInfo.FLAG_OCCLUDES_KEYGUARD;
58 import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
59 import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
60 import static android.window.TransitionInfo.FLAG_WILL_IME_SHOWN;
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.content.pm.ActivityInfo;
72 import android.graphics.Point;
73 import android.graphics.Rect;
74 import android.hardware.HardwareBuffer;
75 import android.os.Binder;
76 import android.os.IBinder;
77 import android.os.IRemoteCallback;
78 import android.os.RemoteException;
79 import android.os.SystemClock;
80 import android.os.Trace;
81 import android.util.ArrayMap;
82 import android.util.ArraySet;
83 import android.util.Slog;
84 import android.util.SparseArray;
85 import android.view.Display;
86 import android.view.SurfaceControl;
87 import android.view.WindowManager;
88 import android.view.animation.Animation;
89 import android.window.RemoteTransition;
90 import android.window.TransitionInfo;
91 
92 import com.android.internal.annotations.VisibleForTesting;
93 import com.android.internal.graphics.ColorUtils;
94 import com.android.internal.protolog.ProtoLogGroup;
95 import com.android.internal.protolog.common.ProtoLog;
96 import com.android.internal.util.function.pooled.PooledLambda;
97 import com.android.server.inputmethod.InputMethodManagerInternal;
98 import com.android.server.wm.utils.RotationAnimationUtils;
99 
100 import java.lang.annotation.Retention;
101 import java.lang.annotation.RetentionPolicy;
102 import java.lang.ref.WeakReference;
103 import java.util.ArrayList;
104 import java.util.List;
105 import java.util.Objects;
106 import java.util.function.Predicate;
107 
108 /**
109  * Represents a logical transition.
110  * @see TransitionController
111  */
112 class Transition implements BLASTSyncEngine.TransactionReadyListener {
113     private static final String TAG = "Transition";
114     private static final String TRACE_NAME_PLAY_TRANSITION = "PlayTransition";
115 
116     /** The default package for resources */
117     private static final String DEFAULT_PACKAGE = "android";
118 
119     /** The transition has been created but isn't collecting yet. */
120     private static final int STATE_PENDING = -1;
121 
122     /** The transition has been created and is collecting, but hasn't formally started. */
123     private static final int STATE_COLLECTING = 0;
124 
125     /**
126      * The transition has formally started. It is still collecting but will stop once all
127      * participants are ready to animate (finished drawing).
128      */
129     private static final int STATE_STARTED = 1;
130 
131     /**
132      * This transition is currently playing its animation and can no longer collect or be changed.
133      */
134     private static final int STATE_PLAYING = 2;
135 
136     /**
137      * This transition is aborting or has aborted. No animation will play nor will anything get
138      * sent to the player.
139      */
140     private static final int STATE_ABORT = 3;
141 
142     /**
143      * This transition has finished playing successfully.
144      */
145     private static final int STATE_FINISHED = 4;
146 
147     @IntDef(prefix = { "STATE_" }, value = {
148             STATE_PENDING,
149             STATE_COLLECTING,
150             STATE_STARTED,
151             STATE_PLAYING,
152             STATE_ABORT,
153             STATE_FINISHED
154     })
155     @Retention(RetentionPolicy.SOURCE)
156     @interface TransitionState {}
157 
158     final @TransitionType int mType;
159     private int mSyncId = -1;
160     private @TransitionFlags int mFlags;
161     private final TransitionController mController;
162     private final BLASTSyncEngine mSyncEngine;
163     private final Token mToken;
164     private RemoteTransition mRemoteTransition = null;
165 
166     /** Only use for clean-up after binder death! */
167     private SurfaceControl.Transaction mStartTransaction = null;
168     private SurfaceControl.Transaction mFinishTransaction = null;
169 
170     /**
171      * Contains change infos for both participants and all remote-animatable ancestors. The
172      * ancestors can be the promotion candidates so their start-states need to be captured.
173      * @see #getAnimatableParent
174      */
175     final ArrayMap<WindowContainer, ChangeInfo> mChanges = new ArrayMap<>();
176 
177     /** The collected participants in the transition. */
178     final ArraySet<WindowContainer> mParticipants = new ArraySet<>();
179 
180     /** The final animation targets derived from participants after promotion. */
181     private ArrayList<WindowContainer> mTargets;
182 
183     /** The displays that this transition is running on. */
184     private final ArrayList<DisplayContent> mTargetDisplays = new ArrayList<>();
185 
186     /**
187      * Set of participating windowtokens (activity/wallpaper) which are visible at the end of
188      * the transition animation.
189      */
190     private final ArraySet<WindowToken> mVisibleAtTransitionEndTokens = new ArraySet<>();
191 
192     /**
193      * Set of transient activities (lifecycle initially tied to this transition) and their
194      * restore-below tasks.
195      */
196     private ArrayMap<ActivityRecord, Task> mTransientLaunches = null;
197 
198     /** Custom activity-level animation options and callbacks. */
199     private TransitionInfo.AnimationOptions mOverrideOptions;
200     private IRemoteCallback mClientAnimationStartCallback = null;
201     private IRemoteCallback mClientAnimationFinishCallback = null;
202 
203     private @TransitionState int mState = STATE_PENDING;
204     private final ReadyTracker mReadyTracker = new ReadyTracker();
205 
206     // TODO(b/188595497): remove when not needed.
207     /** @see RecentsAnimationController#mNavigationBarAttachedToApp */
208     private boolean mNavBarAttachedToApp = false;
209     private int mRecentsDisplayId = INVALID_DISPLAY;
210 
211     /** The delay for light bar appearance animation. */
212     long mStatusBarTransitionDelay;
213 
214     /** @see #setCanPipOnFinish */
215     private boolean mCanPipOnFinish = true;
216 
217     private boolean mIsSeamlessRotation = false;
218     private IContainerFreezer mContainerFreezer = null;
219 
Transition(@ransitionType int type, @TransitionFlags int flags, TransitionController controller, BLASTSyncEngine syncEngine)220     Transition(@TransitionType int type, @TransitionFlags int flags,
221             TransitionController controller, BLASTSyncEngine syncEngine) {
222         mType = type;
223         mFlags = flags;
224         mController = controller;
225         mSyncEngine = syncEngine;
226         mToken = new Token(this);
227 
228         controller.mTransitionTracer.logState(this);
229     }
230 
231     @Nullable
fromBinder(@ullable IBinder token)232     static Transition fromBinder(@Nullable IBinder token) {
233         if (token == null) return null;
234         try {
235             return ((Token) token).mTransition.get();
236         } catch (ClassCastException e) {
237             Slog.w(TAG, "Invalid transition token: " + token, e);
238             return null;
239         }
240     }
241 
242     @NonNull
getToken()243     IBinder getToken() {
244         return mToken;
245     }
246 
addFlag(int flag)247     void addFlag(int flag) {
248         mFlags |= flag;
249     }
250 
251     /** Records an activity as transient-launch. This activity must be already collected. */
setTransientLaunch(@onNull ActivityRecord activity, @Nullable Task restoreBelow)252     void setTransientLaunch(@NonNull ActivityRecord activity, @Nullable Task restoreBelow) {
253         if (mTransientLaunches == null) {
254             mTransientLaunches = new ArrayMap<>();
255         }
256         mTransientLaunches.put(activity, restoreBelow);
257         setTransientLaunchToChanges(activity);
258 
259         if (restoreBelow != null) {
260             final ChangeInfo info = mChanges.get(restoreBelow);
261             if (info != null) {
262                 info.mFlags |= ChangeInfo.FLAG_ABOVE_TRANSIENT_LAUNCH;
263             }
264         }
265         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Transition %d: Set %s as "
266                 + "transient-launch", mSyncId, activity);
267     }
268 
isTransientHide(@onNull Task task)269     boolean isTransientHide(@NonNull Task task) {
270         if (mTransientLaunches == null) return false;
271         for (int i = 0; i < mTransientLaunches.size(); ++i) {
272             if (mTransientLaunches.valueAt(i) == task) {
273                 return true;
274             }
275         }
276         return false;
277     }
278 
isTransientLaunch(@onNull ActivityRecord activity)279     boolean isTransientLaunch(@NonNull ActivityRecord activity) {
280         return mTransientLaunches != null && mTransientLaunches.containsKey(activity);
281     }
282 
getTransientLaunchRestoreTarget(@onNull WindowContainer container)283     Task getTransientLaunchRestoreTarget(@NonNull WindowContainer container) {
284         if (mTransientLaunches == null) return null;
285         for (int i = 0; i < mTransientLaunches.size(); ++i) {
286             if (mTransientLaunches.keyAt(i).isDescendantOf(container)) {
287                 return mTransientLaunches.valueAt(i);
288             }
289         }
290         return null;
291     }
292 
isOnDisplay(@onNull DisplayContent dc)293     boolean isOnDisplay(@NonNull DisplayContent dc) {
294         return mTargetDisplays.contains(dc);
295     }
296 
297     /** Set a transition to be a seamless-rotation. */
setSeamlessRotation(@onNull WindowContainer wc)298     void setSeamlessRotation(@NonNull WindowContainer wc) {
299         final ChangeInfo info = mChanges.get(wc);
300         if (info == null) return;
301         info.mFlags = info.mFlags | ChangeInfo.FLAG_SEAMLESS_ROTATION;
302         onSeamlessRotating(wc.getDisplayContent());
303     }
304 
305     /**
306      * Called when it's been determined that this is transition is a seamless rotation. This should
307      * be called before any WM changes have happened.
308      */
onSeamlessRotating(@onNull DisplayContent dc)309     void onSeamlessRotating(@NonNull DisplayContent dc) {
310         // Don't need to do anything special if everything is using BLAST sync already.
311         if (mSyncEngine.getSyncSet(mSyncId).mSyncMethod == BLASTSyncEngine.METHOD_BLAST) return;
312         if (mContainerFreezer == null) {
313             mContainerFreezer = new ScreenshotFreezer();
314         }
315         final WindowState top = dc.getDisplayPolicy().getTopFullscreenOpaqueWindow();
316         if (top != null) {
317             mIsSeamlessRotation = true;
318             top.mSyncMethodOverride = BLASTSyncEngine.METHOD_BLAST;
319             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Override sync-method for %s "
320                     + "because seamless rotating", top.getName());
321         }
322     }
323 
324     /**
325      * Only set flag to the parent tasks and activity itself.
326      */
setTransientLaunchToChanges(@onNull WindowContainer wc)327     private void setTransientLaunchToChanges(@NonNull WindowContainer wc) {
328         for (WindowContainer curr = wc; curr != null && mChanges.containsKey(curr);
329                 curr = curr.getParent()) {
330             if (curr.asTask() == null && curr.asActivityRecord() == null) {
331                 return;
332             }
333             final ChangeInfo info = mChanges.get(curr);
334             info.mFlags = info.mFlags | ChangeInfo.FLAG_TRANSIENT_LAUNCH;
335         }
336     }
337 
338     /** Only for testing. */
setContainerFreezer(IContainerFreezer freezer)339     void setContainerFreezer(IContainerFreezer freezer) {
340         mContainerFreezer = freezer;
341     }
342 
343     @TransitionState
getState()344     int getState() {
345         return mState;
346     }
347 
348     @VisibleForTesting
getSyncId()349     int getSyncId() {
350         return mSyncId;
351     }
352 
353     @TransitionFlags
getFlags()354     int getFlags() {
355         return mFlags;
356     }
357 
358     @VisibleForTesting
getStartTransaction()359     SurfaceControl.Transaction getStartTransaction() {
360         return mStartTransaction;
361     }
362 
363     @VisibleForTesting
getFinishTransaction()364     SurfaceControl.Transaction getFinishTransaction() {
365         return mFinishTransaction;
366     }
367 
isCollecting()368     boolean isCollecting() {
369         return mState == STATE_COLLECTING || mState == STATE_STARTED;
370     }
371 
372     @VisibleForTesting
startCollecting(long timeoutMs)373     void startCollecting(long timeoutMs) {
374         startCollecting(timeoutMs, TransitionController.SYNC_METHOD);
375     }
376 
377     /** Starts collecting phase. Once this starts, all relevant surface operations are sync. */
startCollecting(long timeoutMs, int method)378     void startCollecting(long timeoutMs, int method) {
379         if (mState != STATE_PENDING) {
380             throw new IllegalStateException("Attempting to re-use a transition");
381         }
382         mState = STATE_COLLECTING;
383         mSyncId = mSyncEngine.startSyncSet(this, timeoutMs, TAG, method);
384 
385         mController.mTransitionTracer.logState(this);
386     }
387 
388     /**
389      * Formally starts the transition. Participants can be collected before this is started,
390      * but this won't consider itself ready until started -- even if all the participants have
391      * drawn.
392      */
start()393     void start() {
394         if (mState < STATE_COLLECTING) {
395             throw new IllegalStateException("Can't start Transition which isn't collecting.");
396         } else if (mState >= STATE_STARTED) {
397             Slog.w(TAG, "Transition already started: " + mSyncId);
398         }
399         mState = STATE_STARTED;
400         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Starting Transition %d",
401                 mSyncId);
402         applyReady();
403 
404         mController.mTransitionTracer.logState(this);
405     }
406 
407     /**
408      * Adds wc to set of WindowContainers participating in this transition.
409      */
collect(@onNull WindowContainer wc)410     void collect(@NonNull WindowContainer wc) {
411         if (mState < STATE_COLLECTING) {
412             throw new IllegalStateException("Transition hasn't started collecting.");
413         }
414         if (!isCollecting()) {
415             // Too late, transition already started playing, so don't collect.
416             return;
417         }
418         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Collecting in transition %d: %s",
419                 mSyncId, wc);
420         // "snapshot" all parents (as potential promotion targets). Do this before checking
421         // if this is already a participant in case it has since been re-parented.
422         for (WindowContainer<?> curr = getAnimatableParent(wc);
423                 curr != null && !mChanges.containsKey(curr);
424                 curr = getAnimatableParent(curr)) {
425             mChanges.put(curr, new ChangeInfo(curr));
426             if (isReadyGroup(curr)) {
427                 mReadyTracker.addGroup(curr);
428                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Creating Ready-group for"
429                                 + " Transition %d with root=%s", mSyncId, curr);
430             }
431         }
432         if (mParticipants.contains(wc)) return;
433         // Wallpaper is like in a static drawn state unless display may have changes, so exclude
434         // the case to reduce transition latency waiting for the unchanged wallpaper to redraw.
435         final boolean needSyncDraw = !isWallpaper(wc) || mParticipants.contains(wc.mDisplayContent);
436         if (needSyncDraw) {
437             mSyncEngine.addToSyncSet(mSyncId, wc);
438         }
439         ChangeInfo info = mChanges.get(wc);
440         if (info == null) {
441             info = new ChangeInfo(wc);
442             mChanges.put(wc, info);
443         }
444         mParticipants.add(wc);
445         if (wc.getDisplayContent() != null && !mTargetDisplays.contains(wc.getDisplayContent())) {
446             mTargetDisplays.add(wc.getDisplayContent());
447         }
448         if (info.mShowWallpaper) {
449             // Collect the wallpaper token (for isWallpaper(wc)) so it is part of the sync set.
450             final WindowState wallpaper =
451                     wc.getDisplayContent().mWallpaperController.getTopVisibleWallpaper();
452             if (wallpaper != null) {
453                 collect(wallpaper.mToken);
454             }
455         }
456     }
457 
458     /**
459      * Records wc as changing its state of existence during this transition. For example, a new
460      * task is considered an existence change while moving a task to front is not. wc is added
461      * to the collection set. Note: Existence is NOT a promotable characteristic.
462      *
463      * This must be explicitly recorded because there are o number of situations where the actual
464      * hierarchy operations don't align with the intent (eg. re-using a task with a new activity
465      * or waiting until after the animation to close).
466      */
collectExistenceChange(@onNull WindowContainer wc)467     void collectExistenceChange(@NonNull WindowContainer wc) {
468         if (mState >= STATE_PLAYING) {
469             // Too late to collect. Don't check too-early here since `collect` will check that.
470             return;
471         }
472         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Existence Changed in transition %d:"
473                 + " %s", mSyncId, wc);
474         collect(wc);
475         mChanges.get(wc).mExistenceChanged = true;
476     }
477 
478     /**
479      * Records that a particular container is changing visibly (ie. something about it is changing
480      * while it remains visible). This only effects windows that are already in the collecting
481      * transition.
482      */
collectVisibleChange(WindowContainer wc)483     void collectVisibleChange(WindowContainer wc) {
484         if (mSyncEngine.getSyncSet(mSyncId).mSyncMethod == BLASTSyncEngine.METHOD_BLAST) {
485             // All windows are synced already.
486             return;
487         }
488         if (!isInTransition(wc)) return;
489 
490         if (mContainerFreezer == null) {
491             mContainerFreezer = new ScreenshotFreezer();
492         }
493         Transition.ChangeInfo change = mChanges.get(wc);
494         if (change == null || !change.mVisible || !wc.isVisibleRequested()) return;
495         // Note: many more tests have already been done by caller.
496         mContainerFreezer.freeze(wc, change.mAbsoluteBounds);
497     }
498 
499     /**
500      * Records that a particular container has been reparented. This only effects windows that have
501      * already been collected in the transition. This should be called before reparenting because
502      * the old parent may be removed during reparenting, for example:
503      * {@link Task#shouldRemoveSelfOnLastChildRemoval}
504      */
collectReparentChange(@onNull WindowContainer wc, @NonNull WindowContainer newParent)505     void collectReparentChange(@NonNull WindowContainer wc, @NonNull WindowContainer newParent) {
506         if (!mChanges.containsKey(wc)) {
507             // #collectReparentChange() will be called when the window is reparented. Skip if it is
508             // a window that has not been collected, which means we don't care about this window for
509             // the current transition.
510             return;
511         }
512         final ChangeInfo change = mChanges.get(wc);
513         // Use the current common ancestor if there are multiple reparent, and the original parent
514         // has been detached. Otherwise, use the original parent before the transition.
515         final WindowContainer prevParent =
516                 change.mStartParent == null || change.mStartParent.isAttached()
517                         ? change.mStartParent
518                         : change.mCommonAncestor;
519         if (prevParent == null || !prevParent.isAttached()) {
520             Slog.w(TAG, "Trying to collect reparenting of a window after the previous parent has"
521                     + " been detached: " + wc);
522             return;
523         }
524         if (prevParent == newParent) {
525             Slog.w(TAG, "Trying to collect reparenting of a window that has not been reparented: "
526                     + wc);
527             return;
528         }
529         if (!newParent.isAttached()) {
530             Slog.w(TAG, "Trying to collect reparenting of a window that is not attached after"
531                     + " reparenting: " + wc);
532             return;
533         }
534         WindowContainer ancestor = newParent;
535         while (prevParent != ancestor && !prevParent.isDescendantOf(ancestor)) {
536             ancestor = ancestor.getParent();
537         }
538         change.mCommonAncestor = ancestor;
539     }
540 
541     /**
542      * @return {@code true} if `wc` is a participant or is a descendant of one.
543      */
isInTransition(WindowContainer wc)544     boolean isInTransition(WindowContainer wc) {
545         for (WindowContainer p = wc; p != null; p = p.getParent()) {
546             if (mParticipants.contains(p)) return true;
547         }
548         return false;
549     }
550 
551     /**
552      * Specifies configuration change explicitly for the window container, so it can be chosen as
553      * transition target. This is usually used with transition mode
554      * {@link android.view.WindowManager#TRANSIT_CHANGE}.
555      */
setKnownConfigChanges(WindowContainer<?> wc, @ActivityInfo.Config int changes)556     void setKnownConfigChanges(WindowContainer<?> wc, @ActivityInfo.Config int changes) {
557         final ChangeInfo changeInfo = mChanges.get(wc);
558         if (changeInfo != null) {
559             changeInfo.mKnownConfigChanges = changes;
560         }
561     }
562 
sendRemoteCallback(@ullable IRemoteCallback callback)563     private void sendRemoteCallback(@Nullable IRemoteCallback callback) {
564         if (callback == null) return;
565         mController.mAtm.mH.sendMessage(PooledLambda.obtainMessage(cb -> {
566             try {
567                 cb.sendResult(null);
568             } catch (RemoteException e) { }
569         }, callback));
570     }
571 
572     /**
573      * Set animation options for collecting transition by ActivityRecord.
574      * @param options AnimationOptions captured from ActivityOptions
575      */
setOverrideAnimation(TransitionInfo.AnimationOptions options, @Nullable IRemoteCallback startCallback, @Nullable IRemoteCallback finishCallback)576     void setOverrideAnimation(TransitionInfo.AnimationOptions options,
577             @Nullable IRemoteCallback startCallback, @Nullable IRemoteCallback finishCallback) {
578         if (!isCollecting()) return;
579         mOverrideOptions = options;
580         sendRemoteCallback(mClientAnimationStartCallback);
581         mClientAnimationStartCallback = startCallback;
582         mClientAnimationFinishCallback = finishCallback;
583     }
584 
585     /**
586      * Call this when all known changes related to this transition have been applied. Until
587      * all participants have finished drawing, the transition can still collect participants.
588      *
589      * If this is called before the transition is started, it will be deferred until start.
590      *
591      * @param wc A reference point to determine which ready-group to update. For now, each display
592      *           has its own ready-group, so this is used to look-up which display to mark ready.
593      *           The transition will wait for all groups to be ready.
594      */
setReady(WindowContainer wc, boolean ready)595     void setReady(WindowContainer wc, boolean ready) {
596         if (!isCollecting() || mSyncId < 0) return;
597         mReadyTracker.setReadyFrom(wc, ready);
598         applyReady();
599     }
600 
applyReady()601     private void applyReady() {
602         if (mState < STATE_STARTED) return;
603         final boolean ready = mReadyTracker.allReady();
604         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
605                 "Set transition ready=%b %d", ready, mSyncId);
606         mSyncEngine.setReady(mSyncId, ready);
607     }
608 
609     /**
610      * Sets all possible ready groups to ready.
611      * @see ReadyTracker#setAllReady.
612      */
setAllReady()613     void setAllReady() {
614         if (!isCollecting() || mSyncId < 0) return;
615         mReadyTracker.setAllReady();
616         applyReady();
617     }
618 
619     @VisibleForTesting
allReady()620     boolean allReady() {
621         return mReadyTracker.allReady();
622     }
623 
624     /**
625      * Build a transaction that "resets" all the re-parenting and layer changes. This is
626      * intended to be applied at the end of the transition but before the finish callback. This
627      * needs to be passed/applied in shell because until finish is called, shell owns the surfaces.
628      * Additionally, this gives shell the ability to better deal with merged transitions.
629      */
buildFinishTransaction(SurfaceControl.Transaction t, SurfaceControl rootLeash)630     private void buildFinishTransaction(SurfaceControl.Transaction t, SurfaceControl rootLeash) {
631         final Point tmpPos = new Point();
632         // usually only size 1
633         final ArraySet<DisplayContent> displays = new ArraySet<>();
634         for (int i = mTargets.size() - 1; i >= 0; --i) {
635             final WindowContainer target = mTargets.get(i);
636             if (target.getParent() != null) {
637                 final SurfaceControl targetLeash = getLeashSurface(target, null /* t */);
638                 final SurfaceControl origParent = getOrigParentSurface(target);
639                 // Ensure surfaceControls are re-parented back into the hierarchy.
640                 t.reparent(targetLeash, origParent);
641                 t.setLayer(targetLeash, target.getLastLayer());
642                 target.getRelativePosition(tmpPos);
643                 t.setPosition(targetLeash, tmpPos.x, tmpPos.y);
644                 // No need to clip the display in case seeing the clipped content when during the
645                 // display rotation. No need to clip activities because they rely on clipping on
646                 // task layers.
647                 if (target.asTaskFragment() == null) {
648                     t.setCrop(targetLeash, null /* crop */);
649                 } else {
650                     // Crop to the resolved override bounds.
651                     final Rect clipRect = target.getResolvedOverrideBounds();
652                     t.setWindowCrop(targetLeash, clipRect.width(), clipRect.height());
653                 }
654                 t.setCornerRadius(targetLeash, 0);
655                 t.setShadowRadius(targetLeash, 0);
656                 t.setMatrix(targetLeash, 1, 0, 0, 1);
657                 t.setAlpha(targetLeash, 1);
658                 // The bounds sent to the transition is always a real bounds. This means we lose
659                 // information about "null" bounds (inheriting from parent). Core will fix-up
660                 // non-organized window surface bounds; however, since Core can't touch organized
661                 // surfaces, add the "inherit from parent" restoration here.
662                 if (target.isOrganized() && target.matchParentBounds()) {
663                     t.setWindowCrop(targetLeash, -1, -1);
664                 }
665                 displays.add(target.getDisplayContent());
666             }
667         }
668         // Remove screenshot layers if necessary
669         if (mContainerFreezer != null) {
670             mContainerFreezer.cleanUp(t);
671         }
672         // Need to update layers on involved displays since they were all paused while
673         // the animation played. This puts the layers back into the correct order.
674         mController.mBuildingFinishLayers = true;
675         try {
676             for (int i = displays.size() - 1; i >= 0; --i) {
677                 if (displays.valueAt(i) == null) continue;
678                 displays.valueAt(i).assignChildLayers(t);
679             }
680         } finally {
681             mController.mBuildingFinishLayers = false;
682         }
683         if (rootLeash.isValid()) {
684             t.reparent(rootLeash, null);
685         }
686     }
687 
688     /**
689      * Set whether this transition can start a pip-enter transition when finished. This is usually
690      * true, but gets set to false when recents decides that it wants to finish its animation but
691      * not actually finish its animation (yeah...).
692      */
setCanPipOnFinish(boolean canPipOnFinish)693     void setCanPipOnFinish(boolean canPipOnFinish) {
694         mCanPipOnFinish = canPipOnFinish;
695     }
696 
didCommitTransientLaunch()697     private boolean didCommitTransientLaunch() {
698         if (mTransientLaunches == null) return false;
699         for (int j = 0; j < mTransientLaunches.size(); ++j) {
700             if (mTransientLaunches.keyAt(j).isVisibleRequested()) {
701                 return true;
702             }
703         }
704         return false;
705     }
706 
707     /**
708      * Check if pip-entry is possible after finishing and enter-pip if it is.
709      *
710      * @return true if we are *guaranteed* to enter-pip. This means we return false if there's
711      *         a chance we won't thus legacy-entry (via pause+userLeaving) will return false.
712      */
checkEnterPipOnFinish(@onNull ActivityRecord ar)713     private boolean checkEnterPipOnFinish(@NonNull ActivityRecord ar) {
714         if (!mCanPipOnFinish || !ar.isVisible() || ar.getTask() == null) return false;
715 
716         if (ar.pictureInPictureArgs != null && ar.pictureInPictureArgs.isAutoEnterEnabled()) {
717             if (didCommitTransientLaunch()) {
718                 // force enable pip-on-task-switch now that we've committed to actually launching
719                 // to the transient activity.
720                 ar.supportsEnterPipOnTaskSwitch = true;
721             }
722             return mController.mAtm.enterPictureInPictureMode(ar, ar.pictureInPictureArgs,
723                     false /* fromClient */);
724         }
725 
726         // Legacy pip-entry (not via isAutoEnterEnabled).
727         boolean canPip = ar.getDeferHidingClient();
728         if (!canPip && didCommitTransientLaunch()) {
729             // force enable pip-on-task-switch now that we've committed to actually launching to the
730             // transient activity, and then recalculate whether we can attempt pip.
731             ar.supportsEnterPipOnTaskSwitch = true;
732             canPip = ar.checkEnterPictureInPictureState(
733                     "finishTransition", true /* beforeStopping */)
734                     && ar.isState(RESUMED);
735         }
736         if (!canPip) return false;
737         try {
738             // Legacy PIP-enter requires pause event with user-leaving.
739             mController.mAtm.mTaskSupervisor.mUserLeaving = true;
740             ar.getTaskFragment().startPausing(false /* uiSleeping */,
741                     null /* resuming */, "finishTransition");
742         } finally {
743             mController.mAtm.mTaskSupervisor.mUserLeaving = false;
744         }
745         // Return false anyway because there's no guarantee that the app will enter pip.
746         return false;
747     }
748 
749     /**
750      * The transition has finished animating and is ready to finalize WM state. This should not
751      * be called directly; use {@link TransitionController#finishTransition} instead.
752      */
finishTransition()753     void finishTransition() {
754         if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
755             Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, TRACE_NAME_PLAY_TRANSITION,
756                     System.identityHashCode(this));
757         }
758         // Close the transactions now. They were originally copied to Shell in case we needed to
759         // apply them due to a remote failure. Since we don't need to apply them anymore, free them
760         // immediately.
761         if (mStartTransaction != null) mStartTransaction.close();
762         if (mFinishTransaction != null) mFinishTransaction.close();
763         mStartTransaction = mFinishTransaction = null;
764         if (mState < STATE_PLAYING) {
765             throw new IllegalStateException("Can't finish a non-playing transition " + mSyncId);
766         }
767 
768         boolean hasParticipatedDisplay = false;
769         // Commit all going-invisible containers
770         for (int i = 0; i < mParticipants.size(); ++i) {
771             final WindowContainer<?> participant = mParticipants.valueAt(i);
772             final ActivityRecord ar = participant.asActivityRecord();
773             if (ar != null) {
774                 boolean visibleAtTransitionEnd = mVisibleAtTransitionEndTokens.contains(ar);
775                 // We need both the expected visibility AND current requested-visibility to be
776                 // false. If it is expected-visible but not currently visible, it means that
777                 // another animation is queued-up to animate this to invisibility, so we can't
778                 // remove the surfaces yet. If it is currently visible, but not expected-visible,
779                 // then doing commitVisibility here would actually be out-of-order and leave the
780                 // activity in a bad state.
781                 // TODO (b/243755838) Create a screen off transition to correct the visible status
782                 // of activities.
783                 final boolean isScreenOff = ar.mDisplayContent == null
784                         || ar.mDisplayContent.getDisplayInfo().state == Display.STATE_OFF;
785                 if ((!visibleAtTransitionEnd || isScreenOff) && !ar.isVisibleRequested()) {
786                     final boolean commitVisibility = !checkEnterPipOnFinish(ar);
787                     // Avoid commit visibility if entering pip or else we will get a sudden
788                     // "flash" / surface going invisible for a split second.
789                     if (commitVisibility) {
790                         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
791                                 "  Commit activity becoming invisible: %s", ar);
792                         final Task task = ar.getTask();
793                         if (task != null && !task.isVisibleRequested()
794                                 && mTransientLaunches != null) {
795                             // If transition is transient, then snapshots are taken at end of
796                             // transition.
797                             mController.mTaskSnapshotController.recordTaskSnapshot(
798                                     task, false /* allowSnapshotHome */);
799                         }
800                         ar.commitVisibility(false /* visible */, false /* performLayout */,
801                                 true /* fromTransition */);
802                     }
803                 }
804                 if (mChanges.get(ar).mVisible != visibleAtTransitionEnd) {
805                     // Legacy dispatch relies on this (for now).
806                     ar.mEnteringAnimation = visibleAtTransitionEnd;
807                 } else if (mTransientLaunches != null && mTransientLaunches.containsKey(ar)
808                         && ar.isVisible()) {
809                     // Transient launch was committed, so report enteringAnimation
810                     ar.mEnteringAnimation = true;
811                 }
812                 continue;
813             }
814             if (participant.asDisplayContent() != null) {
815                 hasParticipatedDisplay = true;
816                 continue;
817             }
818             final WallpaperWindowToken wt = participant.asWallpaperToken();
819             if (wt != null) {
820                 final boolean visibleAtTransitionEnd = mVisibleAtTransitionEndTokens.contains(wt);
821                 if (!visibleAtTransitionEnd && !wt.isVisibleRequested()) {
822                     ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
823                             "  Commit wallpaper becoming invisible: %s", wt);
824                     wt.commitVisibility(false /* visible */);
825                 }
826             }
827         }
828         // dispatch legacy callback in a different loop. This is because multiple legacy handlers
829         // (fixed-rotation/displaycontent) make global changes, so we want to ensure that we've
830         // processed all the participants first (in particular, we want to trigger pip-enter first)
831         for (int i = 0; i < mParticipants.size(); ++i) {
832             final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord();
833             if (ar != null) {
834                 mController.dispatchLegacyAppTransitionFinished(ar);
835             }
836         }
837 
838         // Update the input-sink (touch-blocking) state now that the animation is finished.
839         SurfaceControl.Transaction inputSinkTransaction = null;
840         for (int i = 0; i < mParticipants.size(); ++i) {
841             final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord();
842             if (ar == null || !ar.isVisible() || ar.getParent() == null) continue;
843             if (inputSinkTransaction == null) {
844                 inputSinkTransaction = new SurfaceControl.Transaction();
845             }
846             ar.mActivityRecordInputSink.applyChangesToSurfaceIfChanged(inputSinkTransaction);
847         }
848         if (inputSinkTransaction != null) inputSinkTransaction.apply();
849 
850         // Always schedule stop processing when transition finishes because activities don't
851         // stop while they are in a transition thus their stop could still be pending.
852         mController.mAtm.mTaskSupervisor
853                 .scheduleProcessStoppingAndFinishingActivitiesIfNeeded();
854 
855         sendRemoteCallback(mClientAnimationFinishCallback);
856 
857         legacyRestoreNavigationBarFromApp();
858 
859         if (mRecentsDisplayId != INVALID_DISPLAY) {
860             // Clean up input monitors (for recents)
861             final DisplayContent dc =
862                     mController.mAtm.mRootWindowContainer.getDisplayContent(mRecentsDisplayId);
863             dc.getInputMonitor().setActiveRecents(null /* activity */, null /* layer */);
864         }
865 
866         for (int i = 0; i < mTargetDisplays.size(); ++i) {
867             final DisplayContent dc = mTargetDisplays.get(i);
868             final AsyncRotationController asyncRotationController = dc.getAsyncRotationController();
869             if (asyncRotationController != null && mTargets.contains(dc)) {
870                 asyncRotationController.onTransitionFinished();
871             }
872             if (mTransientLaunches != null) {
873                 InsetsControlTarget prevImeTarget = dc.getImeTarget(
874                         DisplayContent.IME_TARGET_CONTROL);
875                 InsetsControlTarget newImeTarget = null;
876                 // Transient-launch activities cannot be IME target (WindowState#canBeImeTarget),
877                 // so re-compute in case the IME target is changed after transition.
878                 for (int t = 0; t < mTransientLaunches.size(); ++t) {
879                     if (mTransientLaunches.keyAt(t).getDisplayContent() == dc) {
880                         newImeTarget = dc.computeImeTarget(true /* updateImeTarget */);
881                         break;
882                     }
883                 }
884                 if (mRecentsDisplayId != INVALID_DISPLAY && prevImeTarget == newImeTarget) {
885                     // Restore IME icon only when moving the original app task to front from
886                     // recents, in case IME icon may missing if the moving task has already been
887                     // the current focused task.
888                     InputMethodManagerInternal.get().updateImeWindowStatus(
889                             false /* disableImeIcon */);
890                 }
891             }
892             dc.removeImeSurfaceImmediately();
893             dc.handleCompleteDeferredRemoval();
894         }
895 
896         mState = STATE_FINISHED;
897         mController.mTransitionTracer.logState(this);
898         // Rotation change may be deferred while there is a display change transition, so check
899         // again in case there is a new pending change.
900         if (hasParticipatedDisplay && !mController.useShellTransitionsRotation()) {
901             mController.mAtm.mWindowManager.updateRotation(false /* alwaysSendConfiguration */,
902                     false /* forceRelayout */);
903         }
904         cleanUpInternal();
905     }
906 
abort()907     void abort() {
908         // This calls back into itself via controller.abort, so just early return here.
909         if (mState == STATE_ABORT) return;
910         if (mState != STATE_COLLECTING && mState != STATE_STARTED) {
911             throw new IllegalStateException("Too late to abort. state=" + mState);
912         }
913         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Aborting Transition: %d", mSyncId);
914         mState = STATE_ABORT;
915         // Syncengine abort will call through to onTransactionReady()
916         mSyncEngine.abort(mSyncId);
917         mController.dispatchLegacyAppTransitionCancelled();
918     }
919 
setRemoteTransition(RemoteTransition remoteTransition)920     void setRemoteTransition(RemoteTransition remoteTransition) {
921         mRemoteTransition = remoteTransition;
922     }
923 
getRemoteTransition()924     RemoteTransition getRemoteTransition() {
925         return mRemoteTransition;
926     }
927 
928     @Override
onTransactionReady(int syncId, SurfaceControl.Transaction transaction)929     public void onTransactionReady(int syncId, SurfaceControl.Transaction transaction) {
930         if (syncId != mSyncId) {
931             Slog.e(TAG, "Unexpected Sync ID " + syncId + ". Expected " + mSyncId);
932             return;
933         }
934         if (mTargetDisplays.isEmpty()) {
935             mTargetDisplays.add(mController.mAtm.mRootWindowContainer.getDefaultDisplay());
936         }
937         // While there can be multiple DC's involved. For now, we just use the first one as
938         // the "primary" one for most things. Eventually, this will need to change, but, for the
939         // time being, we don't have full cross-display transitions so it isn't a problem.
940         final DisplayContent dc = mTargetDisplays.get(0);
941 
942         if (mState == STATE_ABORT) {
943             mController.abort(this);
944             dc.getPendingTransaction().merge(transaction);
945             mSyncId = -1;
946             mOverrideOptions = null;
947             cleanUpInternal();
948             return;
949         }
950 
951         mState = STATE_PLAYING;
952         mStartTransaction = transaction;
953         mFinishTransaction = mController.mAtm.mWindowManager.mTransactionFactory.get();
954         mController.moveToPlaying(this);
955 
956         if (dc.isKeyguardLocked()) {
957             mFlags |= TRANSIT_FLAG_KEYGUARD_LOCKED;
958         }
959 
960         // Resolve the animating targets from the participants
961         mTargets = calculateTargets(mParticipants, mChanges);
962         final TransitionInfo info = calculateTransitionInfo(mType, mFlags, mTargets, mChanges,
963                 transaction);
964         if (mOverrideOptions != null) {
965             info.setAnimationOptions(mOverrideOptions);
966             if (mOverrideOptions.getType() == ANIM_OPEN_CROSS_PROFILE_APPS) {
967                 for (int i = 0; i < mTargets.size(); ++i) {
968                     final TransitionInfo.Change c = info.getChanges().get(i);
969                     final ActivityRecord ar = mTargets.get(i).asActivityRecord();
970                     if (ar == null || c.getMode() != TRANSIT_OPEN) continue;
971                     int flags = c.getFlags();
972                     flags |= ar.mUserId == ar.mWmService.mCurrentUserId
973                             ? TransitionInfo.FLAG_CROSS_PROFILE_OWNER_THUMBNAIL
974                             : TransitionInfo.FLAG_CROSS_PROFILE_WORK_THUMBNAIL;
975                     c.setFlags(flags);
976                     break;
977                 }
978             }
979         }
980 
981         // TODO(b/188669821): Move to animation impl in shell.
982         handleLegacyRecentsStartBehavior(dc, info);
983 
984         handleNonAppWindowsInTransition(dc, mType, mFlags);
985 
986         // The callback is only populated for custom activity-level client animations
987         sendRemoteCallback(mClientAnimationStartCallback);
988 
989         // Manually show any activities that are visibleRequested. This is needed to properly
990         // support simultaneous animation queueing/merging. Specifically, if transition A makes
991         // an activity invisible, it's finishTransaction (which is applied *after* the animation)
992         // will hide the activity surface. If transition B then makes the activity visible again,
993         // the normal surfaceplacement logic won't add a show to this start transaction because
994         // the activity visibility hasn't been committed yet. To deal with this, we have to manually
995         // show here in the same way that we manually hide in finishTransaction.
996         for (int i = mParticipants.size() - 1; i >= 0; --i) {
997             final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord();
998             if (ar == null || !ar.isVisibleRequested()) continue;
999             transaction.show(ar.getSurfaceControl());
1000 
1001             // Also manually show any non-reported parents. This is necessary in a few cases
1002             // where a task is NOT organized but had its visibility changed within its direct
1003             // parent. An example of this is if an alternate home leaf-task HB is started atop the
1004             // normal home leaf-task HA: these are both in the Home root-task HR, so there will be a
1005             // transition containing HA and HB where HA surface is hidden. If a standard task SA is
1006             // launched on top, then HB finishes, no transition will happen since neither home is
1007             // visible. When SA finishes, the transition contains HR rather than HA. Since home
1008             // leaf-tasks are NOT organized, HA won't be in the transition and thus its surface
1009             // wouldn't be shown. Just show is safe here since all other properties will have
1010             // already been reset by the original hiding-transition's finishTransaction (we can't
1011             // show in the finishTransaction because by then the activity doesn't hide until
1012             // surface placement).
1013             for (WindowContainer p = ar.getParent(); p != null && !mTargets.contains(p);
1014                     p = p.getParent()) {
1015                 if (p.getSurfaceControl() != null) {
1016                     transaction.show(p.getSurfaceControl());
1017                 }
1018             }
1019         }
1020 
1021         // Record windowtokens (activity/wallpaper) that are expected to be visible after the
1022         // transition animation. This will be used in finishTransition to prevent prematurely
1023         // committing visibility.
1024         for (int i = mParticipants.size() - 1; i >= 0; --i) {
1025             final WindowContainer wc = mParticipants.valueAt(i);
1026             if (wc.asWindowToken() == null || !wc.isVisibleRequested()) continue;
1027             // don't include transient launches, though, since those are only temporarily visible.
1028             if (mTransientLaunches != null && wc.asActivityRecord() != null
1029                     && mTransientLaunches.containsKey(wc.asActivityRecord())) continue;
1030             mVisibleAtTransitionEndTokens.add(wc.asWindowToken());
1031         }
1032 
1033         // Take task snapshots before the animation so that we can capture IME before it gets
1034         // transferred. If transition is transient, IME won't be moved during the transition and
1035         // the tasks are still live, so we take the snapshot at the end of the transition instead.
1036         if (mTransientLaunches == null) {
1037             for (int i = mParticipants.size() - 1; i >= 0; --i) {
1038                 final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord();
1039                 if (ar == null || ar.isVisibleRequested() || ar.getTask() == null
1040                         || ar.getTask().isVisibleRequested()) continue;
1041                 mController.mTaskSnapshotController.recordTaskSnapshot(
1042                         ar.getTask(), false /* allowSnapshotHome */);
1043             }
1044         }
1045 
1046         // This is non-null only if display has changes. It handles the visible windows that don't
1047         // need to be participated in the transition.
1048         final AsyncRotationController controller = dc.getAsyncRotationController();
1049         if (controller != null && mTargets.contains(dc)) {
1050             controller.setupStartTransaction(transaction);
1051         }
1052         buildFinishTransaction(mFinishTransaction, info.getRootLeash());
1053         if (mController.getTransitionPlayer() != null) {
1054             mController.dispatchLegacyAppTransitionStarting(info, mStatusBarTransitionDelay);
1055             try {
1056                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
1057                         "Calling onTransitionReady: %s", info);
1058                 mController.getTransitionPlayer().onTransitionReady(
1059                         mToken, info, transaction, mFinishTransaction);
1060                 // Since we created root-leash but no longer reference it from core, release it now
1061                 info.releaseAnimSurfaces();
1062                 if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
1063                     Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, TRACE_NAME_PLAY_TRANSITION,
1064                             System.identityHashCode(this));
1065                 }
1066             } catch (RemoteException e) {
1067                 // If there's an exception when trying to send the mergedTransaction to the
1068                 // client, we should finish and apply it here so the transactions aren't lost.
1069                 cleanUpOnFailure();
1070             }
1071         } else {
1072             // No player registered, so just finish/apply immediately
1073             cleanUpOnFailure();
1074         }
1075         mOverrideOptions = null;
1076 
1077         reportStartReasonsToLogger();
1078     }
1079 
1080     /**
1081      * If the remote failed for any reason, use this to do any appropriate clean-up. Do not call
1082      * this directly, it's designed to by called by {@link TransitionController} only.
1083      */
cleanUpOnFailure()1084     void cleanUpOnFailure() {
1085         // No need to clean-up if this isn't playing yet.
1086         if (mState < STATE_PLAYING) return;
1087 
1088         if (mStartTransaction != null) {
1089             mStartTransaction.apply();
1090         }
1091         if (mFinishTransaction != null) {
1092             mFinishTransaction.apply();
1093         }
1094         mController.finishTransition(mToken);
1095     }
1096 
cleanUpInternal()1097     private void cleanUpInternal() {
1098         // Clean-up any native references.
1099         for (int i = 0; i < mChanges.size(); ++i) {
1100             final ChangeInfo ci = mChanges.valueAt(i);
1101             if (ci.mSnapshot != null) {
1102                 ci.mSnapshot.release();
1103             }
1104         }
1105     }
1106 
1107     /** @see RecentsAnimationController#attachNavigationBarToApp */
handleLegacyRecentsStartBehavior(DisplayContent dc, TransitionInfo info)1108     private void handleLegacyRecentsStartBehavior(DisplayContent dc, TransitionInfo info) {
1109         if ((mFlags & TRANSIT_FLAG_IS_RECENTS) == 0) {
1110             return;
1111         }
1112         mRecentsDisplayId = dc.mDisplayId;
1113 
1114         // Recents has an input-consumer to grab input from the "live tile" app. Set that up here
1115         final InputConsumerImpl recentsAnimationInputConsumer =
1116                 dc.getInputMonitor().getInputConsumer(INPUT_CONSUMER_RECENTS_ANIMATION);
1117         if (recentsAnimationInputConsumer != null) {
1118             // find the top-most going-away activity and the recents activity. The top-most
1119             // is used as layer reference while the recents is used for registering the consumer
1120             // override.
1121             ActivityRecord recentsActivity = null;
1122             ActivityRecord topActivity = null;
1123             for (int i = 0; i < info.getChanges().size(); ++i) {
1124                 final TransitionInfo.Change change = info.getChanges().get(i);
1125                 if (change.getTaskInfo() == null) continue;
1126                 final Task task = Task.fromWindowContainerToken(
1127                         info.getChanges().get(i).getTaskInfo().token);
1128                 if (task == null) continue;
1129                 final int activityType = change.getTaskInfo().topActivityType;
1130                 final boolean isRecents = activityType == ACTIVITY_TYPE_HOME
1131                         || activityType == ACTIVITY_TYPE_RECENTS;
1132                 if (isRecents && recentsActivity == null) {
1133                     recentsActivity = task.getTopVisibleActivity();
1134                 } else if (!isRecents && topActivity == null) {
1135                     topActivity = task.getTopNonFinishingActivity();
1136                 }
1137             }
1138             if (recentsActivity != null && topActivity != null) {
1139                 recentsAnimationInputConsumer.mWindowHandle.touchableRegion.set(
1140                         topActivity.getBounds());
1141                 dc.getInputMonitor().setActiveRecents(recentsActivity, topActivity);
1142             }
1143         }
1144 
1145         // The rest of this function handles nav-bar reparenting
1146 
1147         if (!dc.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition()
1148                 // Skip the case where the nav bar is controlled by fade rotation.
1149                 || dc.getAsyncRotationController() != null) {
1150             return;
1151         }
1152 
1153         WindowContainer topWC = null;
1154         // Find the top-most non-home, closing app.
1155         for (int i = 0; i < info.getChanges().size(); ++i) {
1156             final TransitionInfo.Change c = info.getChanges().get(i);
1157             if (c.getTaskInfo() == null || c.getTaskInfo().displayId != mRecentsDisplayId
1158                     || c.getTaskInfo().getActivityType() != ACTIVITY_TYPE_STANDARD
1159                     || !(c.getMode() == TRANSIT_CLOSE || c.getMode() == TRANSIT_TO_BACK)) {
1160                 continue;
1161             }
1162             topWC = WindowContainer.fromBinder(c.getContainer().asBinder());
1163             break;
1164         }
1165         if (topWC == null || topWC.inMultiWindowMode()) {
1166             return;
1167         }
1168 
1169         final WindowState navWindow = dc.getDisplayPolicy().getNavigationBar();
1170         if (navWindow == null || navWindow.mToken == null) {
1171             return;
1172         }
1173         mNavBarAttachedToApp = true;
1174         navWindow.mToken.cancelAnimation();
1175         final SurfaceControl.Transaction t = navWindow.mToken.getPendingTransaction();
1176         final SurfaceControl navSurfaceControl = navWindow.mToken.getSurfaceControl();
1177         t.reparent(navSurfaceControl, topWC.getSurfaceControl());
1178         t.show(navSurfaceControl);
1179 
1180         final WindowContainer imeContainer = dc.getImeContainer();
1181         if (imeContainer.isVisible()) {
1182             t.setRelativeLayer(navSurfaceControl, imeContainer.getSurfaceControl(), 1);
1183         } else {
1184             // Place the nav bar on top of anything else in the top activity.
1185             t.setLayer(navSurfaceControl, Integer.MAX_VALUE);
1186         }
1187         if (mController.mStatusBar != null) {
1188             mController.mStatusBar.setNavigationBarLumaSamplingEnabled(mRecentsDisplayId, false);
1189         }
1190     }
1191 
1192     /** @see RecentsAnimationController#restoreNavigationBarFromApp */
legacyRestoreNavigationBarFromApp()1193     void legacyRestoreNavigationBarFromApp() {
1194         if (!mNavBarAttachedToApp) return;
1195         mNavBarAttachedToApp = false;
1196 
1197         if (mRecentsDisplayId == INVALID_DISPLAY) {
1198             Slog.e(TAG, "Reparented navigation bar without a valid display");
1199             mRecentsDisplayId = DEFAULT_DISPLAY;
1200         }
1201 
1202         if (mController.mStatusBar != null) {
1203             mController.mStatusBar.setNavigationBarLumaSamplingEnabled(mRecentsDisplayId, true);
1204         }
1205 
1206         final DisplayContent dc =
1207                 mController.mAtm.mRootWindowContainer.getDisplayContent(mRecentsDisplayId);
1208         final WindowState navWindow = dc.getDisplayPolicy().getNavigationBar();
1209         if (navWindow == null) return;
1210         navWindow.setSurfaceTranslationY(0);
1211 
1212         final WindowToken navToken = navWindow.mToken;
1213         if (navToken == null) return;
1214         final SurfaceControl.Transaction t = dc.getPendingTransaction();
1215         final WindowContainer parent = navToken.getParent();
1216         t.setLayer(navToken.getSurfaceControl(), navToken.getLastLayer());
1217 
1218         boolean animate = false;
1219         // Search for the home task. If it is supposed to be visible, then the navbar is not at
1220         // the bottom of the screen, so we need to animate it.
1221         for (int i = 0; i < mTargets.size(); ++i) {
1222             final Task task = mTargets.get(i).asTask();
1223             if (task == null || !task.isActivityTypeHomeOrRecents()) continue;
1224             animate = task.isVisibleRequested();
1225             break;
1226         }
1227 
1228         if (animate) {
1229             final NavBarFadeAnimationController controller =
1230                     new NavBarFadeAnimationController(dc);
1231             controller.fadeWindowToken(true);
1232         } else {
1233             // Reparent the SurfaceControl of nav bar token back.
1234             t.reparent(navToken.getSurfaceControl(), parent.getSurfaceControl());
1235         }
1236     }
1237 
handleNonAppWindowsInTransition(@onNull DisplayContent dc, @TransitionType int transit, @TransitionFlags int flags)1238     private void handleNonAppWindowsInTransition(@NonNull DisplayContent dc,
1239             @TransitionType int transit, @TransitionFlags int flags) {
1240         if ((transit == TRANSIT_KEYGUARD_GOING_AWAY
1241                 || (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0)
1242                 && !WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation) {
1243             if ((flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0
1244                     && (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION) == 0
1245                     && (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION) == 0) {
1246                 Animation anim = mController.mAtm.mWindowManager.mPolicy
1247                         .createKeyguardWallpaperExit(
1248                                 (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE) != 0);
1249                 if (anim != null) {
1250                     anim.scaleCurrentDuration(
1251                             mController.mAtm.mWindowManager.getTransitionAnimationScaleLocked());
1252                     dc.mWallpaperController.startWallpaperAnimation(anim);
1253                 }
1254             }
1255             dc.startKeyguardExitOnNonAppWindows(
1256                     (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0,
1257                     (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE) != 0,
1258                     (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION) != 0);
1259             if (!WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation) {
1260                 // When remote animation is enabled for KEYGUARD_GOING_AWAY transition, SysUI
1261                 // receives IRemoteAnimationRunner#onAnimationStart to start animation, so we don't
1262                 // need to call IKeyguardService#keyguardGoingAway here.
1263                 mController.mAtm.mWindowManager.mPolicy.startKeyguardExitAnimation(
1264                         SystemClock.uptimeMillis(), 0 /* duration */);
1265             }
1266         }
1267         if ((flags & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0) {
1268             mController.mAtm.mWindowManager.mPolicy.applyKeyguardOcclusionChange(
1269                     true /* keyguardOccludingStarted */);
1270         }
1271     }
1272 
reportStartReasonsToLogger()1273     private void reportStartReasonsToLogger() {
1274         // Record transition start in metrics logger. We just assume everything is "DRAWN"
1275         // at this point since splash-screen is a presentation (shell) detail.
1276         ArrayMap<WindowContainer, Integer> reasons = new ArrayMap<>();
1277         for (int i = mParticipants.size() - 1; i >= 0; --i) {
1278             ActivityRecord r = mParticipants.valueAt(i).asActivityRecord();
1279             if (r == null || !r.isVisibleRequested()) continue;
1280             int transitionReason = APP_TRANSITION_WINDOWS_DRAWN;
1281             // At this point, r is "ready", but if it's not "ALL ready" then it is probably only
1282             // ready due to starting-window.
1283             if (r.mStartingData instanceof SplashScreenStartingData && !r.mLastAllReadyAtSync) {
1284                 transitionReason = APP_TRANSITION_SPLASH_SCREEN;
1285             } else if (r.isActivityTypeHomeOrRecents() && isTransientLaunch(r)) {
1286                 transitionReason = APP_TRANSITION_RECENTS_ANIM;
1287             }
1288             reasons.put(r, transitionReason);
1289         }
1290         mController.mAtm.mTaskSupervisor.getActivityMetricsLogger().notifyTransitionStarting(
1291                 reasons);
1292     }
1293 
1294     @Override
toString()1295     public String toString() {
1296         StringBuilder sb = new StringBuilder(64);
1297         sb.append("TransitionRecord{");
1298         sb.append(Integer.toHexString(System.identityHashCode(this)));
1299         sb.append(" id=" + mSyncId);
1300         sb.append(" type=" + transitTypeToString(mType));
1301         sb.append(" flags=" + mFlags);
1302         sb.append('}');
1303         return sb.toString();
1304     }
1305 
1306     /** Returns the parent that the remote animator can animate or control. */
getAnimatableParent(WindowContainer<?> wc)1307     private static WindowContainer<?> getAnimatableParent(WindowContainer<?> wc) {
1308         WindowContainer<?> parent = wc.getParent();
1309         while (parent != null
1310                 && (!parent.canCreateRemoteAnimationTarget() && !parent.isOrganized())) {
1311             parent = parent.getParent();
1312         }
1313         return parent;
1314     }
1315 
reportIfNotTop(WindowContainer wc)1316     private static boolean reportIfNotTop(WindowContainer wc) {
1317         // Organized tasks need to be reported anyways because Core won't show() their surfaces
1318         // and we can't rely on onTaskAppeared because it isn't in sync.
1319         // TODO(shell-transitions): switch onTaskAppeared usage over to transitions OPEN.
1320         return wc.isOrganized();
1321     }
1322 
isWallpaper(WindowContainer wc)1323     private static boolean isWallpaper(WindowContainer wc) {
1324         return wc.asWallpaperToken() != null;
1325     }
1326 
isInputMethod(WindowContainer wc)1327     private static boolean isInputMethod(WindowContainer wc) {
1328         return wc.getWindowType() == TYPE_INPUT_METHOD;
1329     }
1330 
occludesKeyguard(WindowContainer wc)1331     private static boolean occludesKeyguard(WindowContainer wc) {
1332         final ActivityRecord ar = wc.asActivityRecord();
1333         if (ar != null) {
1334             return ar.canShowWhenLocked();
1335         }
1336         final Task t = wc.asTask();
1337         if (t != null) {
1338             // Get the top activity which was visible (since this is going away, it will remain
1339             // client visible until the transition is finished).
1340             // skip hidden (or about to hide) apps
1341             final ActivityRecord top = t.getActivity(WindowToken::isClientVisible);
1342             return top != null && top.canShowWhenLocked();
1343         }
1344         return false;
1345     }
1346 
isTranslucent(@onNull WindowContainer wc)1347     private static boolean isTranslucent(@NonNull WindowContainer wc) {
1348         final TaskFragment taskFragment = wc.asTaskFragment();
1349         if (taskFragment != null) {
1350             if (taskFragment.isTranslucent(null /* starting */)) {
1351                 return true;
1352             }
1353             final TaskFragment adjacentTaskFragment = taskFragment.getAdjacentTaskFragment();
1354             if (adjacentTaskFragment != null) {
1355                 // Treat the TaskFragment as translucent if its adjacent TF is, otherwise everything
1356                 // behind two adjacent TaskFragments are occluded.
1357                 return adjacentTaskFragment.isTranslucent(null /* starting */);
1358             }
1359         }
1360         // TODO(b/172695805): hierarchical check. This is non-trivial because for containers
1361         //                    it is effected by child visibility but needs to work even
1362         //                    before visibility is committed. This means refactoring some
1363         //                    checks to use requested visibility.
1364         return !wc.fillsParent();
1365     }
1366 
1367     /**
1368      * Under some conditions (eg. all visible targets within a parent container are transitioning
1369      * the same way) the transition can be "promoted" to the parent container. This means an
1370      * animation can play just on the parent rather than all the individual children.
1371      *
1372      * @return {@code true} if transition in target can be promoted to its parent.
1373      */
canPromote(WindowContainer<?> target, Targets targets, ArrayMap<WindowContainer, ChangeInfo> changes)1374     private static boolean canPromote(WindowContainer<?> target, Targets targets,
1375             ArrayMap<WindowContainer, ChangeInfo> changes) {
1376         final WindowContainer<?> parent = target.getParent();
1377         final ChangeInfo parentChange = changes.get(parent);
1378         if (!parent.canCreateRemoteAnimationTarget()
1379                 || parentChange == null || !parentChange.hasChanged(parent)) {
1380             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "      SKIP: %s",
1381                     "parent can't be target " + parent);
1382             return false;
1383         }
1384         if (isWallpaper(target)) {
1385             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "      SKIP: is wallpaper");
1386             return false;
1387         }
1388 
1389         final ChangeInfo change = changes.get(target);
1390         if (change.mStartParent != null && target.getParent() != change.mStartParent) {
1391             // When a window is reparented, the state change won't fit into any of the parents.
1392             // Don't promote such change so that we can animate the reparent if needed.
1393             return false;
1394         }
1395 
1396         final @TransitionInfo.TransitionMode int mode = change.getTransitMode(target);
1397         for (int i = parent.getChildCount() - 1; i >= 0; --i) {
1398             final WindowContainer<?> sibling = parent.getChildAt(i);
1399             if (target == sibling) continue;
1400             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "      check sibling %s",
1401                     sibling);
1402             final ChangeInfo siblingChange = changes.get(sibling);
1403             if (siblingChange == null || !targets.wasParticipated(sibling)) {
1404                 if (sibling.isVisibleRequested()) {
1405                     // Sibling is visible but not animating, so no promote.
1406                     ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
1407                             "        SKIP: sibling is visible but not part of transition");
1408                     return false;
1409                 }
1410                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
1411                         "        unrelated invisible sibling %s", sibling);
1412                 continue;
1413             }
1414 
1415             final int siblingMode = siblingChange.getTransitMode(sibling);
1416             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
1417                     "        sibling is a participant with mode %s",
1418                     TransitionInfo.modeToString(siblingMode));
1419             if (reduceMode(mode) != reduceMode(siblingMode)) {
1420                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
1421                         "          SKIP: common mode mismatch. was %s",
1422                         TransitionInfo.modeToString(mode));
1423                 return false;
1424             }
1425         }
1426         return true;
1427     }
1428 
1429     /** "reduces" a mode into a smaller set of modes that uniquely represents visibility change. */
1430     @TransitionInfo.TransitionMode
reduceMode(@ransitionInfo.TransitionMode int mode)1431     private static int reduceMode(@TransitionInfo.TransitionMode int mode) {
1432         switch (mode) {
1433             case TRANSIT_TO_BACK: return TRANSIT_CLOSE;
1434             case TRANSIT_TO_FRONT: return TRANSIT_OPEN;
1435             default: return mode;
1436         }
1437     }
1438 
1439     /**
1440      * Go through topTargets and try to promote (see {@link #canPromote}) one of them.
1441      *
1442      * @param targets all targets that will be sent to the player.
1443      */
tryPromote(Targets targets, ArrayMap<WindowContainer, ChangeInfo> changes)1444     private static void tryPromote(Targets targets, ArrayMap<WindowContainer, ChangeInfo> changes) {
1445         WindowContainer<?> lastNonPromotableParent = null;
1446         // Go through from the deepest target.
1447         for (int i = targets.mArray.size() - 1; i >= 0; --i) {
1448             final WindowContainer<?> target = targets.mArray.valueAt(i);
1449             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "    checking %s", target);
1450             final WindowContainer<?> parent = target.getParent();
1451             if (parent == lastNonPromotableParent) {
1452                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
1453                         "      SKIP: its sibling was rejected");
1454                 continue;
1455             }
1456             if (!canPromote(target, targets, changes)) {
1457                 lastNonPromotableParent = parent;
1458                 continue;
1459             }
1460             if (reportIfNotTop(target)) {
1461                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
1462                         "        keep as target %s", target);
1463             } else {
1464                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
1465                         "        remove from targets %s", target);
1466                 targets.remove(i, target);
1467             }
1468             if (targets.mArray.indexOfValue(parent) < 0) {
1469                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
1470                         "      CAN PROMOTE: promoting to parent %s", parent);
1471                 // The parent has lower depth, so it will be checked in the later iteration.
1472                 i++;
1473                 targets.add(parent);
1474             }
1475         }
1476     }
1477 
1478     /**
1479      * Find WindowContainers to be animated from a set of opening and closing apps. We will promote
1480      * animation targets to higher level in the window hierarchy if possible.
1481      */
1482     @VisibleForTesting
1483     @NonNull
calculateTargets(ArraySet<WindowContainer> participants, ArrayMap<WindowContainer, ChangeInfo> changes)1484     static ArrayList<WindowContainer> calculateTargets(ArraySet<WindowContainer> participants,
1485             ArrayMap<WindowContainer, ChangeInfo> changes) {
1486         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
1487                 "Start calculating TransitionInfo based on participants: %s", participants);
1488 
1489         // Add all valid participants to the target container.
1490         final Targets targets = new Targets();
1491         for (int i = participants.size() - 1; i >= 0; --i) {
1492             final WindowContainer<?> wc = participants.valueAt(i);
1493             if (!wc.isAttached()) {
1494                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
1495                         "  Rejecting as detached: %s", wc);
1496                 continue;
1497             }
1498             // The level of transition target should be at least window token.
1499             if (wc.asWindowState() != null) continue;
1500 
1501             final ChangeInfo changeInfo = changes.get(wc);
1502 
1503             // Reject no-ops
1504             if (!changeInfo.hasChanged(wc)) {
1505                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
1506                         "  Rejecting as no-op: %s", wc);
1507                 continue;
1508             }
1509             targets.add(wc);
1510         }
1511         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "  Initial targets: %s",
1512                 targets.mArray);
1513         // Combine the targets from bottom to top if possible.
1514         tryPromote(targets, changes);
1515         // Establish the relationship between the targets and their top changes.
1516         populateParentChanges(targets, changes);
1517 
1518         final ArrayList<WindowContainer> targetList = targets.getListSortedByZ();
1519         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "  Final targets: %s", targetList);
1520         return targetList;
1521     }
1522 
1523     /** Populates parent to the change info and collects intermediate targets. */
populateParentChanges(Targets targets, ArrayMap<WindowContainer, ChangeInfo> changes)1524     private static void populateParentChanges(Targets targets,
1525             ArrayMap<WindowContainer, ChangeInfo> changes) {
1526         final ArrayList<WindowContainer<?>> intermediates = new ArrayList<>();
1527         // Make a copy to iterate because the original array may be modified.
1528         final ArrayList<WindowContainer<?>> targetList = new ArrayList<>(targets.mArray.size());
1529         for (int i = targets.mArray.size() - 1; i >= 0; --i) {
1530             targetList.add(targets.mArray.valueAt(i));
1531         }
1532         for (int i = targetList.size() - 1; i >= 0; --i) {
1533             final WindowContainer<?> wc = targetList.get(i);
1534             // Wallpaper must belong to the top (regardless of how nested it is in DisplayAreas).
1535             final boolean skipIntermediateReports = isWallpaper(wc);
1536             intermediates.clear();
1537             boolean foundParentInTargets = false;
1538             // Collect the intermediate parents between target and top changed parent.
1539             for (WindowContainer<?> p = getAnimatableParent(wc); p != null;
1540                     p = getAnimatableParent(p)) {
1541                 final ChangeInfo parentChange = changes.get(p);
1542                 if (parentChange == null || !parentChange.hasChanged(p)) break;
1543                 if (p.mRemoteToken == null) {
1544                     // Intermediate parents must be those that has window to be managed by Shell.
1545                     continue;
1546                 }
1547                 if (parentChange.mEndParent != null && !skipIntermediateReports) {
1548                     changes.get(wc).mEndParent = p;
1549                     // The chain above the parent was processed.
1550                     break;
1551                 }
1552                 if (targetList.contains(p)) {
1553                     if (skipIntermediateReports) {
1554                         changes.get(wc).mEndParent = p;
1555                     } else {
1556                         intermediates.add(p);
1557                     }
1558                     foundParentInTargets = true;
1559                     break;
1560                 } else if (reportIfNotTop(p) && !skipIntermediateReports) {
1561                     intermediates.add(p);
1562                 }
1563             }
1564             if (!foundParentInTargets || intermediates.isEmpty()) continue;
1565             // Add any always-report parents along the way.
1566             changes.get(wc).mEndParent = intermediates.get(0);
1567             for (int j = 0; j < intermediates.size() - 1; j++) {
1568                 final WindowContainer<?> intermediate = intermediates.get(j);
1569                 changes.get(intermediate).mEndParent = intermediates.get(j + 1);
1570                 targets.add(intermediate);
1571             }
1572         }
1573     }
1574 
1575     /**
1576      * Gets the leash surface for a window container.
1577      * @param t a transaction to create leashes on when necessary (fixed rotation at token-level).
1578      *          If t is null, then this will not create any leashes, just use one if it is there --
1579      *          this is relevant for building the finishTransaction since it needs to match the
1580      *          start state and not erroneously create a leash of its own.
1581      */
getLeashSurface(WindowContainer wc, @Nullable SurfaceControl.Transaction t)1582     private static SurfaceControl getLeashSurface(WindowContainer wc,
1583             @Nullable SurfaceControl.Transaction t) {
1584         final DisplayContent asDC = wc.asDisplayContent();
1585         if (asDC != null) {
1586             // DisplayContent is the "root", so we use the windowing layer instead to avoid
1587             // hardware-screen-level surfaces.
1588             return asDC.getWindowingLayer();
1589         }
1590         if (!wc.mTransitionController.useShellTransitionsRotation()) {
1591             final WindowToken asToken = wc.asWindowToken();
1592             if (asToken != null) {
1593                 // WindowTokens can have a fixed-rotation applied to them. In the current
1594                 // implementation this fact is hidden from the player, so we must create a leash.
1595                 final SurfaceControl leash = t != null ? asToken.getOrCreateFixedRotationLeash(t)
1596                         : asToken.getFixedRotationLeash();
1597                 if (leash != null) return leash;
1598             }
1599         }
1600         return wc.getSurfaceControl();
1601     }
1602 
getOrigParentSurface(WindowContainer wc)1603     private static SurfaceControl getOrigParentSurface(WindowContainer wc) {
1604         if (wc.asDisplayContent() != null) {
1605             // DisplayContent is the "root", so we reinterpret it's wc as the window layer
1606             // making the parent surface the displaycontent's surface.
1607             return wc.getSurfaceControl();
1608         }
1609         return wc.getParent().getSurfaceControl();
1610     }
1611 
1612     /**
1613      * A ready group is defined by a root window-container where all transitioning windows under
1614      * it are expected to animate together as a group. At the moment, this treats each display as
1615      * a ready-group to match the existing legacy transition behavior.
1616      */
isReadyGroup(WindowContainer wc)1617     private static boolean isReadyGroup(WindowContainer wc) {
1618         return wc instanceof DisplayContent;
1619     }
1620 
1621     /**
1622      * Construct a TransitionInfo object from a set of targets and changes. Also populates the
1623      * root surface.
1624      * @param sortedTargets The targets sorted by z-order from top (index 0) to bottom.
1625      * @param startT The start transaction - used to set-up new leashes.
1626      */
1627     @VisibleForTesting
1628     @NonNull
calculateTransitionInfo(@ransitionType int type, int flags, ArrayList<WindowContainer> sortedTargets, ArrayMap<WindowContainer, ChangeInfo> changes, @Nullable SurfaceControl.Transaction startT)1629     static TransitionInfo calculateTransitionInfo(@TransitionType int type, int flags,
1630             ArrayList<WindowContainer> sortedTargets,
1631             ArrayMap<WindowContainer, ChangeInfo> changes,
1632             @Nullable SurfaceControl.Transaction startT) {
1633         final TransitionInfo out = new TransitionInfo(type, flags);
1634 
1635         WindowContainer<?> topApp = null;
1636         for (int i = 0; i < sortedTargets.size(); i++) {
1637             final WindowContainer<?> wc = sortedTargets.get(i);
1638             if (!isWallpaper(wc)) {
1639                 topApp = wc;
1640                 break;
1641             }
1642         }
1643         if (topApp == null) {
1644             out.setRootLeash(new SurfaceControl(), 0, 0);
1645             return out;
1646         }
1647 
1648         WindowContainer<?> ancestor = findCommonAncestor(sortedTargets, changes, topApp);
1649 
1650         // Make leash based on highest (z-order) direct child of ancestor with a participant.
1651         // TODO(b/261418859): Handle the case when the target contains window containers which
1652         // belong to a different display. As a workaround we use topApp, from which wallpaper
1653         // window container is removed, instead of sortedTargets here.
1654         WindowContainer leashReference = topApp;
1655         while (leashReference.getParent() != ancestor) {
1656             leashReference = leashReference.getParent();
1657         }
1658         final SurfaceControl rootLeash = leashReference.makeAnimationLeash().setName(
1659                 "Transition Root: " + leashReference.getName()).build();
1660         startT.setLayer(rootLeash, leashReference.getLastLayer());
1661         out.setRootLeash(rootLeash, ancestor.getBounds().left, ancestor.getBounds().top);
1662 
1663         // Convert all the resolved ChangeInfos into TransactionInfo.Change objects in order.
1664         final int count = sortedTargets.size();
1665         for (int i = 0; i < count; ++i) {
1666             final WindowContainer target = sortedTargets.get(i);
1667             final ChangeInfo info = changes.get(target);
1668             final TransitionInfo.Change change = new TransitionInfo.Change(
1669                     target.mRemoteToken != null ? target.mRemoteToken.toWindowContainerToken()
1670                             : null, getLeashSurface(target, startT));
1671             // TODO(shell-transitions): Use leash for non-organized windows.
1672             if (info.mEndParent != null) {
1673                 change.setParent(info.mEndParent.mRemoteToken.toWindowContainerToken());
1674             }
1675             if (info.mStartParent != null && info.mStartParent.mRemoteToken != null
1676                     && target.getParent() != info.mStartParent) {
1677                 change.setLastParent(info.mStartParent.mRemoteToken.toWindowContainerToken());
1678             }
1679             change.setMode(info.getTransitMode(target));
1680             change.setStartAbsBounds(info.mAbsoluteBounds);
1681             change.setFlags(info.getChangeFlags(target));
1682 
1683             final Task task = target.asTask();
1684             final TaskFragment taskFragment = target.asTaskFragment();
1685             final ActivityRecord activityRecord = target.asActivityRecord();
1686 
1687             if (task != null) {
1688                 final ActivityManager.RunningTaskInfo tinfo = new ActivityManager.RunningTaskInfo();
1689                 task.fillTaskInfo(tinfo);
1690                 change.setTaskInfo(tinfo);
1691                 change.setRotationAnimation(getTaskRotationAnimation(task));
1692                 final ActivityRecord topMostActivity = task.getTopMostActivity();
1693                 change.setAllowEnterPip(topMostActivity != null
1694                         && topMostActivity.checkEnterPictureInPictureAppOpsState());
1695                 final ActivityRecord topRunningActivity = task.topRunningActivity();
1696                 if (topRunningActivity != null && task.mDisplayContent != null
1697                         // Display won't be rotated for multi window Task, so the fixed rotation
1698                         // won't be applied. This can happen when the windowing mode is changed
1699                         // before the previous fixed rotation is applied.
1700                         && !task.inMultiWindowMode()) {
1701                     // If Activity is in fixed rotation, its will be applied with the next rotation,
1702                     // when the Task is still in the previous rotation.
1703                     final int taskRotation = task.getWindowConfiguration().getDisplayRotation();
1704                     final int activityRotation = topRunningActivity.getWindowConfiguration()
1705                             .getDisplayRotation();
1706                     if (taskRotation != activityRotation) {
1707                         change.setEndFixedRotation(activityRotation);
1708                     }
1709                 }
1710             } else if ((info.mFlags & ChangeInfo.FLAG_SEAMLESS_ROTATION) != 0) {
1711                 change.setRotationAnimation(ROTATION_ANIMATION_SEAMLESS);
1712             }
1713 
1714             final WindowContainer<?> parent = target.getParent();
1715             final Rect bounds = target.getBounds();
1716             final Rect parentBounds = parent.getBounds();
1717             change.setEndRelOffset(bounds.left - parentBounds.left,
1718                     bounds.top - parentBounds.top);
1719             int endRotation = target.getWindowConfiguration().getRotation();
1720             if (activityRecord != null) {
1721                 // TODO(b/227427984): Shell needs to aware letterbox.
1722                 // Always use parent bounds of activity because letterbox area (e.g. fixed aspect
1723                 // ratio or size compat mode) should be included in the animation.
1724                 change.setEndAbsBounds(parentBounds);
1725                 if (activityRecord.getRelativeDisplayRotation() != 0
1726                         && !activityRecord.mTransitionController.useShellTransitionsRotation()) {
1727                     // Use parent rotation because shell doesn't know the surface is rotated.
1728                     endRotation = parent.getWindowConfiguration().getRotation();
1729                 }
1730             } else {
1731                 change.setEndAbsBounds(bounds);
1732             }
1733 
1734             if (activityRecord != null || (taskFragment != null && taskFragment.isEmbedded())) {
1735                 final int backgroundColor;
1736                 final TaskFragment organizedTf = activityRecord != null
1737                         ? activityRecord.getOrganizedTaskFragment()
1738                         : taskFragment.getOrganizedTaskFragment();
1739                 if (organizedTf != null && organizedTf.getAnimationParams()
1740                         .getAnimationBackgroundColor() != DEFAULT_ANIMATION_BACKGROUND_COLOR) {
1741                     // This window is embedded and has an animation background color set on the
1742                     // TaskFragment. Pass this color with this window, so the handler can use it as
1743                     // the animation background color if needed,
1744                     backgroundColor = organizedTf.getAnimationParams()
1745                             .getAnimationBackgroundColor();
1746                 } else {
1747                     // Set background color to Task theme color for activity and embedded
1748                     // TaskFragment in case we want to show background during the animation.
1749                     final Task parentTask = activityRecord != null
1750                             ? activityRecord.getTask()
1751                             : taskFragment.getTask();
1752                     backgroundColor = parentTask.getTaskDescription().getBackgroundColor();
1753                 }
1754                 // Set to opaque for animation background to prevent it from exposing the blank
1755                 // background or content below.
1756                 change.setBackgroundColor(ColorUtils.setAlphaComponent(backgroundColor, 255));
1757             }
1758 
1759             change.setRotation(info.mRotation, endRotation);
1760             if (info.mSnapshot != null) {
1761                 change.setSnapshot(info.mSnapshot, info.mSnapshotLuma);
1762             }
1763 
1764             out.addChange(change);
1765         }
1766 
1767         final WindowManager.LayoutParams animLp =
1768                 getLayoutParamsForAnimationsStyle(type, sortedTargets);
1769         if (animLp != null && animLp.type != TYPE_APPLICATION_STARTING
1770                 && animLp.windowAnimations != 0) {
1771             // Don't send animation options if no windowAnimations have been set or if the we are
1772             // running an app starting animation, in which case we don't want the app to be able to
1773             // change its animation directly.
1774             TransitionInfo.AnimationOptions animOptions =
1775                     TransitionInfo.AnimationOptions.makeAnimOptionsFromLayoutParameters(animLp);
1776             out.setAnimationOptions(animOptions);
1777         }
1778 
1779         return out;
1780     }
1781 
1782     /**
1783      * Finds the top-most common ancestor of app targets.
1784      *
1785      * Makes sure that the previous parent is also a descendant to make sure the animation won't
1786      * be covered by other windows below the previous parent. For example, when reparenting an
1787      * activity from PiP Task to split screen Task.
1788      */
1789     @NonNull
findCommonAncestor( @onNull ArrayList<WindowContainer> targets, @NonNull ArrayMap<WindowContainer, ChangeInfo> changes, @NonNull WindowContainer<?> topApp)1790     private static WindowContainer<?> findCommonAncestor(
1791             @NonNull ArrayList<WindowContainer> targets,
1792             @NonNull ArrayMap<WindowContainer, ChangeInfo> changes,
1793             @NonNull WindowContainer<?> topApp) {
1794         WindowContainer<?> ancestor = topApp.getParent();
1795         // Go up ancestor parent chain until all targets are descendants. Ancestor should never be
1796         // null because all targets are attached.
1797         for (int i = targets.size() - 1; i >= 0; i--) {
1798             final WindowContainer wc = targets.get(i);
1799             if (isWallpaper(wc)) {
1800                 // Skip the non-app window.
1801                 continue;
1802             }
1803             while (!wc.isDescendantOf(ancestor)) {
1804                 ancestor = ancestor.getParent();
1805             }
1806 
1807             // Make sure the previous parent is also a descendant to make sure the animation won't
1808             // be covered by other windows below the previous parent. For example, when reparenting
1809             // an activity from PiP Task to split screen Task.
1810             final ChangeInfo change = changes.get(wc);
1811             final WindowContainer prevParent = change.mCommonAncestor;
1812             if (prevParent == null || !prevParent.isAttached()) {
1813                 continue;
1814             }
1815             while (prevParent != ancestor && !prevParent.isDescendantOf(ancestor)) {
1816                 ancestor = ancestor.getParent();
1817             }
1818         }
1819         return ancestor;
1820     }
1821 
getLayoutParamsForAnimationsStyle(int type, ArrayList<WindowContainer> sortedTargets)1822     private static WindowManager.LayoutParams getLayoutParamsForAnimationsStyle(int type,
1823             ArrayList<WindowContainer> sortedTargets) {
1824         // Find the layout params of the top-most application window that is part of the
1825         // transition, which is what will control the animation theme.
1826         final ArraySet<Integer> activityTypes = new ArraySet<>();
1827         for (WindowContainer target : sortedTargets) {
1828             if (target.asActivityRecord() != null) {
1829                 activityTypes.add(target.getActivityType());
1830             } else if (target.asWindowToken() == null && target.asWindowState() == null) {
1831                 // We don't want app to customize animations that are not activity to activity.
1832                 // Activity-level transitions can only include activities, wallpaper and subwindows.
1833                 // Anything else is not a WindowToken nor a WindowState and is "higher" in the
1834                 // hierarchy which means we are no longer in an activity transition.
1835                 return null;
1836             }
1837         }
1838         if (activityTypes.isEmpty()) {
1839             // We don't want app to be able to customize transitions that are not activity to
1840             // activity through the layout parameter animation style.
1841             return null;
1842         }
1843         final ActivityRecord animLpActivity =
1844                 findAnimLayoutParamsActivityRecord(sortedTargets, type, activityTypes);
1845         final WindowState mainWindow = animLpActivity != null
1846                 ? animLpActivity.findMainWindow() : null;
1847         return mainWindow != null ? mainWindow.mAttrs : null;
1848     }
1849 
findAnimLayoutParamsActivityRecord( List<WindowContainer> sortedTargets, @TransitionType int transit, ArraySet<Integer> activityTypes)1850     private static ActivityRecord findAnimLayoutParamsActivityRecord(
1851             List<WindowContainer> sortedTargets,
1852             @TransitionType int transit, ArraySet<Integer> activityTypes) {
1853         // Remote animations always win, but fullscreen windows override non-fullscreen windows.
1854         ActivityRecord result = lookForTopWindowWithFilter(sortedTargets,
1855                 w -> w.getRemoteAnimationDefinition() != null
1856                     && w.getRemoteAnimationDefinition().hasTransition(transit, activityTypes));
1857         if (result != null) {
1858             return result;
1859         }
1860         result = lookForTopWindowWithFilter(sortedTargets,
1861                 w -> w.fillsParent() && w.findMainWindow() != null);
1862         if (result != null) {
1863             return result;
1864         }
1865         return lookForTopWindowWithFilter(sortedTargets, w -> w.findMainWindow() != null);
1866     }
1867 
lookForTopWindowWithFilter(List<WindowContainer> sortedTargets, Predicate<ActivityRecord> filter)1868     private static ActivityRecord lookForTopWindowWithFilter(List<WindowContainer> sortedTargets,
1869             Predicate<ActivityRecord> filter) {
1870         for (WindowContainer target : sortedTargets) {
1871             final ActivityRecord activityRecord = target.asTaskFragment() != null
1872                     ? target.asTaskFragment().getTopNonFinishingActivity()
1873                     : target.asActivityRecord();
1874             if (activityRecord != null && filter.test(activityRecord)) {
1875                 return activityRecord;
1876             }
1877         }
1878         return null;
1879     }
1880 
getTaskRotationAnimation(@onNull Task task)1881     private static int getTaskRotationAnimation(@NonNull Task task) {
1882         final ActivityRecord top = task.getTopVisibleActivity();
1883         if (top == null) return ROTATION_ANIMATION_UNSPECIFIED;
1884         final WindowState mainWin = top.findMainWindow(false);
1885         if (mainWin == null) return ROTATION_ANIMATION_UNSPECIFIED;
1886         int anim = mainWin.getRotationAnimationHint();
1887         if (anim >= 0) return anim;
1888         anim = mainWin.getAttrs().rotationAnimation;
1889         if (anim != ROTATION_ANIMATION_SEAMLESS) return anim;
1890         if (mainWin != task.mDisplayContent.getDisplayPolicy().getTopFullscreenOpaqueWindow()
1891                 || !top.matchParentBounds()) {
1892             // At the moment, we only support seamless rotation if there is only one window showing.
1893             return ROTATION_ANIMATION_UNSPECIFIED;
1894         }
1895         return mainWin.getAttrs().rotationAnimation;
1896     }
1897 
1898     /** Applies the new configuration and returns {@code true} if there is a display change. */
applyDisplayChangeIfNeeded()1899     boolean applyDisplayChangeIfNeeded() {
1900         boolean changed = false;
1901         for (int i = mParticipants.size() - 1; i >= 0; --i) {
1902             final WindowContainer<?> wc = mParticipants.valueAt(i);
1903             final DisplayContent dc = wc.asDisplayContent();
1904             if (dc == null || !mChanges.get(dc).hasChanged(dc)) continue;
1905             dc.sendNewConfiguration();
1906             changed = true;
1907         }
1908         return changed;
1909     }
1910 
getLegacyIsReady()1911     boolean getLegacyIsReady() {
1912         return isCollecting() && mSyncId >= 0;
1913     }
1914 
1915     @VisibleForTesting
1916     static class ChangeInfo {
1917         private static final int FLAG_NONE = 0;
1918 
1919         /**
1920          * When set, the associated WindowContainer has been explicitly requested to be a
1921          * seamless rotation. This is currently only used by DisplayContent during fixed-rotation.
1922          */
1923         private static final int FLAG_SEAMLESS_ROTATION = 1;
1924         private static final int FLAG_TRANSIENT_LAUNCH = 2;
1925         private static final int FLAG_ABOVE_TRANSIENT_LAUNCH = 4;
1926 
1927         @IntDef(prefix = { "FLAG_" }, value = {
1928                 FLAG_NONE,
1929                 FLAG_SEAMLESS_ROTATION,
1930                 FLAG_TRANSIENT_LAUNCH,
1931                 FLAG_ABOVE_TRANSIENT_LAUNCH
1932         })
1933         @Retention(RetentionPolicy.SOURCE)
1934         @interface Flag {}
1935 
1936         /**
1937          * "Parent" that is also included in the transition. When populating the parent changes, we
1938          * may skip the intermediate parents, so this may not be the actual parent in the hierarchy.
1939          */
1940         WindowContainer mEndParent;
1941         /** Actual parent window before change state. */
1942         WindowContainer mStartParent;
1943         /**
1944          * When the window is reparented during the transition, this is the common ancestor window
1945          * of the {@link #mStartParent} and the current parent. This is needed because the
1946          * {@link #mStartParent} may have been detached when the transition starts.
1947          */
1948         WindowContainer mCommonAncestor;
1949 
1950         // State tracking
1951         boolean mExistenceChanged = false;
1952         // before change state
1953         boolean mVisible;
1954         int mWindowingMode;
1955         final Rect mAbsoluteBounds = new Rect();
1956         boolean mShowWallpaper;
1957         int mRotation = ROTATION_UNDEFINED;
1958         @ActivityInfo.Config int mKnownConfigChanges;
1959 
1960         /** These are just extra info. They aren't used for change-detection. */
1961         @Flag int mFlags = FLAG_NONE;
1962 
1963         /** Snapshot surface and luma, if relevant. */
1964         SurfaceControl mSnapshot;
1965         float mSnapshotLuma;
1966 
ChangeInfo(@onNull WindowContainer origState)1967         ChangeInfo(@NonNull WindowContainer origState) {
1968             mVisible = origState.isVisibleRequested();
1969             mWindowingMode = origState.getWindowingMode();
1970             mAbsoluteBounds.set(origState.getBounds());
1971             mShowWallpaper = origState.showWallpaper();
1972             mRotation = origState.getWindowConfiguration().getRotation();
1973             mStartParent = origState.getParent();
1974         }
1975 
1976         @VisibleForTesting
ChangeInfo(boolean visible, boolean existChange)1977         ChangeInfo(boolean visible, boolean existChange) {
1978             mVisible = visible;
1979             mExistenceChanged = existChange;
1980             mShowWallpaper = false;
1981         }
1982 
hasChanged(@onNull WindowContainer newState)1983         boolean hasChanged(@NonNull WindowContainer newState) {
1984             // the task including transient launch must promote to root task
1985             if ((mFlags & ChangeInfo.FLAG_TRANSIENT_LAUNCH) != 0
1986                     || (mFlags & ChangeInfo.FLAG_ABOVE_TRANSIENT_LAUNCH) != 0) {
1987                 return true;
1988             }
1989             // If it's invisible and hasn't changed visibility, always return false since even if
1990             // something changed, it wouldn't be a visible change.
1991             final boolean currVisible = newState.isVisibleRequested();
1992             if (currVisible == mVisible && !mVisible) return false;
1993             return currVisible != mVisible
1994                     || mKnownConfigChanges != 0
1995                     // if mWindowingMode is 0, this container wasn't attached at collect time, so
1996                     // assume no change in windowing-mode.
1997                     || (mWindowingMode != 0 && newState.getWindowingMode() != mWindowingMode)
1998                     || !newState.getBounds().equals(mAbsoluteBounds)
1999                     || mRotation != newState.getWindowConfiguration().getRotation();
2000         }
2001 
2002         @TransitionInfo.TransitionMode
getTransitMode(@onNull WindowContainer wc)2003         int getTransitMode(@NonNull WindowContainer wc) {
2004             if ((mFlags & ChangeInfo.FLAG_ABOVE_TRANSIENT_LAUNCH) != 0) {
2005                 return mExistenceChanged ? TRANSIT_CLOSE : TRANSIT_TO_BACK;
2006             }
2007             final boolean nowVisible = wc.isVisibleRequested();
2008             if (nowVisible == mVisible) {
2009                 return TRANSIT_CHANGE;
2010             }
2011             if (mExistenceChanged) {
2012                 return nowVisible ? TRANSIT_OPEN : TRANSIT_CLOSE;
2013             } else {
2014                 return nowVisible ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK;
2015             }
2016         }
2017 
2018         @TransitionInfo.ChangeFlags
getChangeFlags(@onNull WindowContainer wc)2019         int getChangeFlags(@NonNull WindowContainer wc) {
2020             int flags = 0;
2021             if (mShowWallpaper || wc.showWallpaper()) {
2022                 flags |= FLAG_SHOW_WALLPAPER;
2023             }
2024             if (isTranslucent(wc)) {
2025                 flags |= FLAG_TRANSLUCENT;
2026             }
2027             final Task task = wc.asTask();
2028             if (task != null) {
2029                 final ActivityRecord topActivity = task.getTopNonFinishingActivity();
2030                 if (topActivity != null && topActivity.mStartingData != null
2031                         && topActivity.mStartingData.hasImeSurface()) {
2032                     flags |= FLAG_WILL_IME_SHOWN;
2033                 }
2034                 if (task.voiceSession != null) {
2035                     flags |= FLAG_IS_VOICE_INTERACTION;
2036                 }
2037             }
2038             Task parentTask = null;
2039             final ActivityRecord record = wc.asActivityRecord();
2040             if (record != null) {
2041                 parentTask = record.getTask();
2042                 if (record.mVoiceInteraction) {
2043                     flags |= FLAG_IS_VOICE_INTERACTION;
2044                 }
2045                 flags |= record.mTransitionChangeFlags;
2046             }
2047             final TaskFragment taskFragment = wc.asTaskFragment();
2048             if (taskFragment != null && task == null) {
2049                 parentTask = taskFragment.getTask();
2050             }
2051             if (parentTask != null) {
2052                 if (parentTask.forAllLeafTaskFragments(TaskFragment::isEmbedded)) {
2053                     // Whether this is in a Task with embedded activity.
2054                     flags |= FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
2055                 }
2056                 if (parentTask.forAllActivities(ActivityRecord::hasStartingWindow)) {
2057                     // The starting window should cover all windows inside the leaf Task.
2058                     flags |= FLAG_IS_BEHIND_STARTING_WINDOW;
2059                 }
2060                 if (isWindowFillingTask(wc, parentTask)) {
2061                     // Whether the container fills its parent Task bounds.
2062                     flags |= FLAG_FILLS_TASK;
2063                 }
2064             } else {
2065                 final DisplayContent dc = wc.asDisplayContent();
2066                 if (dc != null) {
2067                     flags |= FLAG_IS_DISPLAY;
2068                     if (dc.hasAlertWindowSurfaces()) {
2069                         flags |= FLAG_DISPLAY_HAS_ALERT_WINDOWS;
2070                     }
2071                 } else if (isWallpaper(wc)) {
2072                     flags |= FLAG_IS_WALLPAPER;
2073                 } else if (isInputMethod(wc)) {
2074                     flags |= FLAG_IS_INPUT_METHOD;
2075                 } else {
2076                     // In this condition, the wc can only be WindowToken or DisplayArea.
2077                     final int type = wc.getWindowType();
2078                     if (type >= WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW
2079                             && type <= WindowManager.LayoutParams.LAST_SYSTEM_WINDOW) {
2080                         flags |= TransitionInfo.FLAG_IS_SYSTEM_WINDOW;
2081                     }
2082                 }
2083             }
2084             if (occludesKeyguard(wc)) {
2085                 flags |= FLAG_OCCLUDES_KEYGUARD;
2086             }
2087             return flags;
2088         }
2089 
2090         /** Whether the container fills its parent Task bounds before and after the transition. */
isWindowFillingTask(@onNull WindowContainer wc, @NonNull Task parentTask)2091         private boolean isWindowFillingTask(@NonNull WindowContainer wc, @NonNull Task parentTask) {
2092             final Rect taskBounds = parentTask.getBounds();
2093             final int taskWidth = taskBounds.width();
2094             final int taskHeight = taskBounds.height();
2095             final Rect startBounds = mAbsoluteBounds;
2096             final Rect endBounds = wc.getBounds();
2097             // Treat it as filling the task if it is not visible.
2098             final boolean isInvisibleOrFillingTaskBeforeTransition = !mVisible
2099                     || (taskWidth == startBounds.width() && taskHeight == startBounds.height());
2100             final boolean isInVisibleOrFillingTaskAfterTransition = !wc.isVisibleRequested()
2101                     || (taskWidth == endBounds.width() && taskHeight == endBounds.height());
2102             return isInvisibleOrFillingTaskBeforeTransition
2103                     && isInVisibleOrFillingTaskAfterTransition;
2104         }
2105     }
2106 
2107     /**
2108      * This transition will be considered not-ready until a corresponding call to
2109      * {@link #continueTransitionReady}
2110      */
deferTransitionReady()2111     void deferTransitionReady() {
2112         ++mReadyTracker.mDeferReadyDepth;
2113         // Make sure it wait until #continueTransitionReady() is called.
2114         mSyncEngine.setReady(mSyncId, false);
2115     }
2116 
2117     /** This undoes one call to {@link #deferTransitionReady}. */
continueTransitionReady()2118     void continueTransitionReady() {
2119         --mReadyTracker.mDeferReadyDepth;
2120         // Apply ready in case it is waiting for the previous defer call.
2121         applyReady();
2122     }
2123 
2124     /**
2125      * The transition sync mechanism has 2 parts:
2126      *   1. Whether all WM operations for a particular transition are "ready" (eg. did the app
2127      *      launch or stop or get a new configuration?).
2128      *   2. Whether all the windows involved have finished drawing their final-state content.
2129      *
2130      * A transition animation can play once both parts are complete. This ready-tracker keeps track
2131      * of part (1). Currently, WM code assumes that "readiness" (part 1) is grouped. This means that
2132      * even if the WM operations in one group are ready, the whole transition itself may not be
2133      * ready if there are WM operations still pending in another group. This class helps keep track
2134      * of readiness across the multiple groups. Currently, we assume that each display is a group
2135      * since that is how it has been until now.
2136      */
2137     private static class ReadyTracker {
2138         private final ArrayMap<WindowContainer, Boolean> mReadyGroups = new ArrayMap<>();
2139 
2140         /**
2141          * Ensures that this doesn't report as allReady before it has been used. This is needed
2142          * in very niche cases where a transition is a no-op (nothing has been collected) but we
2143          * still want to be marked ready (via. setAllReady).
2144          */
2145         private boolean mUsed = false;
2146 
2147         /**
2148          * If true, this overrides all ready groups and reports ready. Used by shell-initiated
2149          * transitions via {@link #setAllReady()}.
2150          */
2151         private boolean mReadyOverride = false;
2152 
2153         /**
2154          * When non-zero, this transition is forced not-ready (even over setAllReady()). Use this
2155          * (via deferTransitionReady/continueTransitionReady) for situations where we want to do
2156          * bulk operations which could trigger surface-placement but the existing ready-state
2157          * isn't known.
2158          */
2159         private int mDeferReadyDepth = 0;
2160 
2161         /**
2162          * Adds a ready-group. Any setReady calls in this subtree will be tracked together. For
2163          * now these are only DisplayContents.
2164          */
addGroup(WindowContainer wc)2165         void addGroup(WindowContainer wc) {
2166             if (mReadyGroups.containsKey(wc)) {
2167                 Slog.e(TAG, "Trying to add a ready-group twice: " + wc);
2168                 return;
2169             }
2170             mReadyGroups.put(wc, false);
2171         }
2172 
2173         /**
2174          * Sets a group's ready state.
2175          * @param wc Any container within a group's subtree. Used to identify the ready-group.
2176          */
setReadyFrom(WindowContainer wc, boolean ready)2177         void setReadyFrom(WindowContainer wc, boolean ready) {
2178             mUsed = true;
2179             WindowContainer current = wc;
2180             while (current != null) {
2181                 if (isReadyGroup(current)) {
2182                     mReadyGroups.put(current, ready);
2183                     ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Setting Ready-group to"
2184                             + " %b. group=%s from %s", ready, current, wc);
2185                     break;
2186                 }
2187                 current = current.getParent();
2188             }
2189         }
2190 
2191         /** Marks this as ready regardless of individual groups. */
setAllReady()2192         void setAllReady() {
2193             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Setting allReady override");
2194             mUsed = true;
2195             mReadyOverride = true;
2196         }
2197 
2198         /** @return true if all tracked subtrees are ready. */
allReady()2199         boolean allReady() {
2200             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " allReady query: used=%b "
2201                     + "override=%b defer=%d states=[%s]", mUsed, mReadyOverride, mDeferReadyDepth,
2202                     groupsToString());
2203             // If the readiness has never been touched, mUsed will be false. We never want to
2204             // consider a transition ready if nothing has been reported on it.
2205             if (!mUsed) return false;
2206             // If we are deferring readiness, we never report ready. This is usually temporary.
2207             if (mDeferReadyDepth > 0) return false;
2208             // Next check all the ready groups to see if they are ready. We can short-cut this if
2209             // ready-override is set (which is treated as "everything is marked ready").
2210             if (mReadyOverride) return true;
2211             for (int i = mReadyGroups.size() - 1; i >= 0; --i) {
2212                 final WindowContainer wc = mReadyGroups.keyAt(i);
2213                 if (!wc.isAttached() || !wc.isVisibleRequested()) continue;
2214                 if (!mReadyGroups.valueAt(i)) return false;
2215             }
2216             return true;
2217         }
2218 
groupsToString()2219         private String groupsToString() {
2220             StringBuilder b = new StringBuilder();
2221             for (int i = 0; i < mReadyGroups.size(); ++i) {
2222                 if (i != 0) b.append(',');
2223                 b.append(mReadyGroups.keyAt(i)).append(':')
2224                         .append(mReadyGroups.valueAt(i));
2225             }
2226             return b.toString();
2227         }
2228     }
2229 
2230     /**
2231      * The container to represent the depth relation for calculating transition targets. The window
2232      * container with larger depth is put at larger index. For the same depth, higher z-order has
2233      * larger index.
2234      */
2235     private static class Targets {
2236         /** All targets. Its keys (depth) are sorted in ascending order naturally. */
2237         final SparseArray<WindowContainer<?>> mArray = new SparseArray<>();
2238         /** The targets which were represented by their parent. */
2239         private ArrayList<WindowContainer<?>> mRemovedTargets;
2240         private int mDepthFactor;
2241 
add(WindowContainer<?> target)2242         void add(WindowContainer<?> target) {
2243             // The number of slots per depth is larger than the total number of window container,
2244             // so the depth score (key) won't have collision.
2245             if (mDepthFactor == 0) {
2246                 mDepthFactor = target.mWmService.mRoot.getTreeWeight() + 1;
2247             }
2248             int score = target.getPrefixOrderIndex();
2249             WindowContainer<?> wc = target;
2250             while (wc != null) {
2251                 final WindowContainer<?> parent = wc.getParent();
2252                 if (parent != null) {
2253                     score += mDepthFactor;
2254                 }
2255                 wc = parent;
2256             }
2257             mArray.put(score, target);
2258         }
2259 
remove(int index, WindowContainer<?> removingTarget)2260         void remove(int index, WindowContainer<?> removingTarget) {
2261             mArray.removeAt(index);
2262             if (mRemovedTargets == null) {
2263                 mRemovedTargets = new ArrayList<>();
2264             }
2265             mRemovedTargets.add(removingTarget);
2266         }
2267 
wasParticipated(WindowContainer<?> wc)2268         boolean wasParticipated(WindowContainer<?> wc) {
2269             return mArray.indexOfValue(wc) >= 0
2270                     || (mRemovedTargets != null && mRemovedTargets.contains(wc));
2271         }
2272 
2273         /** Returns the target list sorted by z-order in ascending order (index 0 is top). */
getListSortedByZ()2274         ArrayList<WindowContainer> getListSortedByZ() {
2275             final SparseArray<WindowContainer<?>> arrayByZ = new SparseArray<>(mArray.size());
2276             for (int i = mArray.size() - 1; i >= 0; --i) {
2277                 final int zOrder = mArray.keyAt(i) % mDepthFactor;
2278                 arrayByZ.put(zOrder, mArray.valueAt(i));
2279             }
2280             final ArrayList<WindowContainer> sortedTargets = new ArrayList<>(arrayByZ.size());
2281             for (int i = arrayByZ.size() - 1; i >= 0; --i) {
2282                 sortedTargets.add(arrayByZ.valueAt(i));
2283             }
2284             return sortedTargets;
2285         }
2286     }
2287 
2288     /**
2289      * Interface for freezing a container's content during sync preparation. Really just one impl
2290      * but broken into an interface for testing (since you can't take screenshots in unit tests).
2291      */
2292     interface IContainerFreezer {
2293         /**
2294          * Makes sure a particular window is "frozen" for the remainder of a sync.
2295          *
2296          * @return whether the freeze was successful. It fails if `wc` is already in a frozen window
2297          *         or is not visible/ready.
2298          */
freeze(@onNull WindowContainer wc, @NonNull Rect bounds)2299         boolean freeze(@NonNull WindowContainer wc, @NonNull Rect bounds);
2300 
2301         /** Populates `t` with operations that clean-up any state created to set-up the freeze. */
cleanUp(SurfaceControl.Transaction t)2302         void cleanUp(SurfaceControl.Transaction t);
2303     }
2304 
2305     /**
2306      * Freezes container content by taking a screenshot. Because screenshots are heavy, usage of
2307      * any container "freeze" is currently explicit. WM code needs to be prudent about which
2308      * containers to freeze.
2309      */
2310     @VisibleForTesting
2311     private class ScreenshotFreezer implements IContainerFreezer {
2312         /** Keeps track of which windows are frozen. Not all frozen windows have snapshots. */
2313         private final ArraySet<WindowContainer> mFrozen = new ArraySet<>();
2314 
2315         /** Takes a screenshot and puts it at the top of the container's surface. */
2316         @Override
freeze(@onNull WindowContainer wc, @NonNull Rect bounds)2317         public boolean freeze(@NonNull WindowContainer wc, @NonNull Rect bounds) {
2318             if (!wc.isVisibleRequested()) return false;
2319 
2320             // Check if any parents have already been "frozen". If so, `wc` is already part of that
2321             // snapshot, so just skip it.
2322             for (WindowContainer p = wc; p != null; p = p.getParent()) {
2323                 if (mFrozen.contains(p)) return false;
2324             }
2325 
2326             if (mIsSeamlessRotation) {
2327                 WindowState top = wc.getDisplayContent() == null ? null
2328                         : wc.getDisplayContent().getDisplayPolicy().getTopFullscreenOpaqueWindow();
2329                 if (top != null && (top == wc || top.isDescendantOf(wc))) {
2330                     // Don't use screenshots for seamless windows: these will use BLAST even if not
2331                     // BLAST mode.
2332                     mFrozen.add(wc);
2333                     return true;
2334                 }
2335             }
2336 
2337             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Screenshotting %s [%s]",
2338                     wc.toString(), bounds.toString());
2339 
2340             Rect cropBounds = new Rect(bounds);
2341             cropBounds.offsetTo(0, 0);
2342             SurfaceControl.LayerCaptureArgs captureArgs =
2343                     new SurfaceControl.LayerCaptureArgs.Builder(wc.getSurfaceControl())
2344                             .setSourceCrop(cropBounds)
2345                             .setCaptureSecureLayers(true)
2346                             .setAllowProtected(true)
2347                             .build();
2348             SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer =
2349                     SurfaceControl.captureLayers(captureArgs);
2350             final HardwareBuffer buffer = screenshotBuffer == null ? null
2351                     : screenshotBuffer.getHardwareBuffer();
2352             if (buffer == null || buffer.getWidth() <= 1 || buffer.getHeight() <= 1) {
2353                 // This can happen when display is not ready.
2354                 Slog.w(TAG, "Failed to capture screenshot for " + wc);
2355                 return false;
2356             }
2357             final boolean isDisplayRotation = wc.asDisplayContent() != null
2358                     && wc.asDisplayContent().isRotationChanging();
2359             // Some tests may check the name "RotationLayer" to detect display rotation.
2360             final String name = isDisplayRotation ? "RotationLayer" : "transition snapshot: " + wc;
2361             SurfaceControl snapshotSurface = wc.makeAnimationLeash()
2362                     .setName(name)
2363                     .setOpaque(true)
2364                     .setParent(wc.getSurfaceControl())
2365                     .setSecure(screenshotBuffer.containsSecureLayers())
2366                     .setCallsite("Transition.ScreenshotSync")
2367                     .setBLASTLayer()
2368                     .build();
2369             mFrozen.add(wc);
2370             final ChangeInfo changeInfo = Objects.requireNonNull(mChanges.get(wc));
2371             changeInfo.mSnapshot = snapshotSurface;
2372             if (isDisplayRotation) {
2373                 // This isn't cheap, so only do it for display rotations.
2374                 changeInfo.mSnapshotLuma = RotationAnimationUtils.getMedianBorderLuma(
2375                         screenshotBuffer.getHardwareBuffer(), screenshotBuffer.getColorSpace());
2376             }
2377             SurfaceControl.Transaction t = wc.mWmService.mTransactionFactory.get();
2378 
2379             t.setBuffer(snapshotSurface, buffer);
2380             t.setDataSpace(snapshotSurface, screenshotBuffer.getColorSpace().getDataSpace());
2381             t.show(snapshotSurface);
2382 
2383             // Place it on top of anything else in the container.
2384             t.setLayer(snapshotSurface, Integer.MAX_VALUE);
2385             t.apply();
2386             t.close();
2387 
2388             // Detach the screenshot on the sync transaction (the screenshot is just meant to
2389             // freeze the window until the sync transaction is applied (with all its other
2390             // corresponding changes), so this is how we unfreeze it.
2391             wc.getSyncTransaction().reparent(snapshotSurface, null /* newParent */);
2392             return true;
2393         }
2394 
2395         @Override
cleanUp(SurfaceControl.Transaction t)2396         public void cleanUp(SurfaceControl.Transaction t) {
2397             for (int i = 0; i < mFrozen.size(); ++i) {
2398                 SurfaceControl snap =
2399                         Objects.requireNonNull(mChanges.get(mFrozen.valueAt(i))).mSnapshot;
2400                 // May be null if it was frozen via BLAST override.
2401                 if (snap == null) continue;
2402                 t.reparent(snap, null /* newParent */);
2403             }
2404         }
2405     }
2406 
2407     private static class Token extends Binder {
2408         final WeakReference<Transition> mTransition;
2409 
Token(Transition transition)2410         Token(Transition transition) {
2411             mTransition = new WeakReference<>(transition);
2412         }
2413 
2414         @Override
toString()2415         public String toString() {
2416             return "Token{" + Integer.toHexString(System.identityHashCode(this)) + " "
2417                     + mTransition.get() + "}";
2418         }
2419     }
2420 }
2421