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.WindowConfiguration.ACTIVITY_TYPE_HOME;
20 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
21 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
22 import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
23 import static android.view.Display.DEFAULT_DISPLAY;
24 import static android.view.Display.INVALID_DISPLAY;
25 import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION;
26 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
27 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
28 import static android.view.WindowManager.TRANSIT_CHANGE;
29 import static android.view.WindowManager.TRANSIT_CLOSE;
30 import static android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS;
31 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
32 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION;
33 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION;
34 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE;
35 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER;
36 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
37 import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
38 import static android.view.WindowManager.TRANSIT_NONE;
39 import static android.view.WindowManager.TRANSIT_OPEN;
40 import static android.view.WindowManager.TRANSIT_TO_BACK;
41 import static android.view.WindowManager.TRANSIT_TO_FRONT;
42 import static android.view.WindowManager.TransitionFlags;
43 import static android.view.WindowManager.TransitionType;
44 import static android.view.WindowManager.transitTypeToString;
45 import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS;
46 import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
47 import static android.window.TransitionInfo.FLAG_IS_VOICE_INTERACTION;
48 import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
49 import static android.window.TransitionInfo.FLAG_OCCLUDES_KEYGUARD;
50 import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
51 import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
52 import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
53 
54 import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_SPLASH_SCREEN;
55 import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_WINDOWS_DRAWN;
56 
57 import android.annotation.IntDef;
58 import android.annotation.NonNull;
59 import android.annotation.Nullable;
60 import android.app.ActivityManager;
61 import android.content.pm.ActivityInfo;
62 import android.graphics.Point;
63 import android.graphics.Rect;
64 import android.os.Binder;
65 import android.os.IBinder;
66 import android.os.IRemoteCallback;
67 import android.os.RemoteException;
68 import android.os.SystemClock;
69 import android.util.ArrayMap;
70 import android.util.ArraySet;
71 import android.util.Slog;
72 import android.view.SurfaceControl;
73 import android.view.animation.Animation;
74 import android.window.RemoteTransition;
75 import android.window.TransitionInfo;
76 
77 import com.android.internal.annotations.VisibleForTesting;
78 import com.android.internal.protolog.ProtoLogGroup;
79 import com.android.internal.protolog.common.ProtoLog;
80 import com.android.internal.util.function.pooled.PooledLambda;
81 
82 import java.lang.annotation.Retention;
83 import java.lang.annotation.RetentionPolicy;
84 import java.util.ArrayList;
85 
86 /**
87  * Represents a logical transition.
88  * @see TransitionController
89  */
90 class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListener {
91     private static final String TAG = "Transition";
92 
93     /** The transition has been created and is collecting, but hasn't formally started. */
94     private static final int STATE_COLLECTING = 0;
95 
96     /**
97      * The transition has formally started. It is still collecting but will stop once all
98      * participants are ready to animate (finished drawing).
99      */
100     private static final int STATE_STARTED = 1;
101 
102     /**
103      * This transition is currently playing its animation and can no longer collect or be changed.
104      */
105     private static final int STATE_PLAYING = 2;
106 
107     /**
108      * This transition is aborting or has aborted. No animation will play nor will anything get
109      * sent to the player.
110      */
111     private static final int STATE_ABORT = 3;
112 
113     @IntDef(prefix = { "STATE_" }, value = {
114             STATE_COLLECTING,
115             STATE_STARTED,
116             STATE_PLAYING,
117             STATE_ABORT
118     })
119     @Retention(RetentionPolicy.SOURCE)
120     @interface TransitionState {}
121 
122     final @TransitionType int mType;
123     private int mSyncId;
124     private @TransitionFlags int mFlags;
125     private final TransitionController mController;
126     private final BLASTSyncEngine mSyncEngine;
127     private RemoteTransition mRemoteTransition = null;
128 
129     /** Only use for clean-up after binder death! */
130     private SurfaceControl.Transaction mStartTransaction = null;
131     private SurfaceControl.Transaction mFinishTransaction = null;
132 
133     /**
134      * Contains change infos for both participants and all ancestors. We have to track ancestors
135      * because they are all promotion candidates and thus we need their start-states
136      * to be captured.
137      */
138     final ArrayMap<WindowContainer, ChangeInfo> mChanges = new ArrayMap<>();
139 
140     /** The collected participants in the transition. */
141     final ArraySet<WindowContainer> mParticipants = new ArraySet<>();
142 
143     /** The final animation targets derived from participants after promotion. */
144     private ArraySet<WindowContainer> mTargets = null;
145 
146     /**
147      * Set of participating windowtokens (activity/wallpaper) which are visible at the end of
148      * the transition animation.
149      */
150     private final ArraySet<WindowToken> mVisibleAtTransitionEndTokens = new ArraySet<>();
151 
152     /** Set of transient activities (lifecycle initially tied to this transition). */
153     private ArraySet<ActivityRecord> mTransientLaunches = null;
154 
155     /** Custom activity-level animation options and callbacks. */
156     private TransitionInfo.AnimationOptions mOverrideOptions;
157     private IRemoteCallback mClientAnimationStartCallback = null;
158     private IRemoteCallback mClientAnimationFinishCallback = null;
159 
160     private @TransitionState int mState = STATE_COLLECTING;
161     private final ReadyTracker mReadyTracker = new ReadyTracker();
162 
163     // TODO(b/188595497): remove when not needed.
164     /** @see RecentsAnimationController#mNavigationBarAttachedToApp */
165     private boolean mNavBarAttachedToApp = false;
166     private int mRecentsDisplayId = INVALID_DISPLAY;
167 
Transition(@ransitionType int type, @TransitionFlags int flags, long timeoutMs, TransitionController controller, BLASTSyncEngine syncEngine)168     Transition(@TransitionType int type, @TransitionFlags int flags, long timeoutMs,
169             TransitionController controller, BLASTSyncEngine syncEngine) {
170         mType = type;
171         mFlags = flags;
172         mController = controller;
173         mSyncEngine = syncEngine;
174         mSyncId = mSyncEngine.startSyncSet(this, timeoutMs);
175     }
176 
addFlag(int flag)177     void addFlag(int flag) {
178         mFlags |= flag;
179     }
180 
181     /** Records an activity as transient-launch. This activity must be already collected. */
setTransientLaunch(@onNull ActivityRecord activity)182     void setTransientLaunch(@NonNull ActivityRecord activity) {
183         if (mTransientLaunches == null) {
184             mTransientLaunches = new ArraySet<>();
185         }
186         mTransientLaunches.add(activity);
187         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Transition %d: Set %s as "
188                 + "transient-launch", mSyncId, activity);
189     }
190 
isTransientLaunch(@onNull ActivityRecord activity)191     boolean isTransientLaunch(@NonNull ActivityRecord activity) {
192         return mTransientLaunches != null && mTransientLaunches.contains(activity);
193     }
194 
195     @VisibleForTesting
getSyncId()196     int getSyncId() {
197         return mSyncId;
198     }
199 
200     @TransitionFlags
getFlags()201     int getFlags() {
202         return mFlags;
203     }
204 
205     /**
206      * Formally starts the transition. Participants can be collected before this is started,
207      * but this won't consider itself ready until started -- even if all the participants have
208      * drawn.
209      */
start()210     void start() {
211         if (mState >= STATE_STARTED) {
212             Slog.w(TAG, "Transition already started: " + mSyncId);
213         }
214         mState = STATE_STARTED;
215         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Starting Transition %d",
216                 mSyncId);
217         applyReady();
218     }
219 
220     /**
221      * Adds wc to set of WindowContainers participating in this transition.
222      */
collect(@onNull WindowContainer wc)223     void collect(@NonNull WindowContainer wc) {
224         if (mSyncId < 0) return;
225         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Collecting in transition %d: %s",
226                 mSyncId, wc);
227         // "snapshot" all parents (as potential promotion targets). Do this before checking
228         // if this is already a participant in case it has since been re-parented.
229         for (WindowContainer curr = wc.getParent(); curr != null && !mChanges.containsKey(curr);
230                 curr = curr.getParent()) {
231             mChanges.put(curr, new ChangeInfo(curr));
232             if (isReadyGroup(curr)) {
233                 mReadyTracker.addGroup(curr);
234                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Creating Ready-group for"
235                                 + " Transition %d with root=%s", mSyncId, curr);
236             }
237         }
238         if (mParticipants.contains(wc)) return;
239         mSyncEngine.addToSyncSet(mSyncId, wc);
240         ChangeInfo info = mChanges.get(wc);
241         if (info == null) {
242             info = new ChangeInfo(wc);
243             mChanges.put(wc, info);
244         }
245         mParticipants.add(wc);
246         if (info.mShowWallpaper) {
247             // Collect the wallpaper so it is part of the sync set.
248             final WindowContainer wallpaper =
249                     wc.getDisplayContent().mWallpaperController.getTopVisibleWallpaper();
250             if (wallpaper != null) {
251                 collect(wallpaper);
252             }
253         }
254     }
255 
256     /**
257      * Records wc as changing its state of existence during this transition. For example, a new
258      * task is considered an existence change while moving a task to front is not. wc is added
259      * to the collection set. Note: Existence is NOT a promotable characteristic.
260      *
261      * This must be explicitly recorded because there are o number of situations where the actual
262      * hierarchy operations don't align with the intent (eg. re-using a task with a new activity
263      * or waiting until after the animation to close).
264      */
collectExistenceChange(@onNull WindowContainer wc)265     void collectExistenceChange(@NonNull WindowContainer wc) {
266         if (mSyncId < 0) return;
267         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Existence Changed in transition %d:"
268                 + " %s", mSyncId, wc);
269         collect(wc);
270         mChanges.get(wc).mExistenceChanged = true;
271     }
272 
273     /**
274      * Specifies configuration change explicitly for the window container, so it can be chosen as
275      * transition target. This is usually used with transition mode
276      * {@link android.view.WindowManager#TRANSIT_CHANGE}.
277      */
setKnownConfigChanges(WindowContainer<?> wc, @ActivityInfo.Config int changes)278     void setKnownConfigChanges(WindowContainer<?> wc, @ActivityInfo.Config int changes) {
279         final ChangeInfo changeInfo = mChanges.get(wc);
280         if (changeInfo != null) {
281             changeInfo.mKnownConfigChanges = changes;
282         }
283     }
284 
sendRemoteCallback(@ullable IRemoteCallback callback)285     private void sendRemoteCallback(@Nullable IRemoteCallback callback) {
286         if (callback == null) return;
287         mController.mAtm.mH.sendMessage(PooledLambda.obtainMessage(cb -> {
288             try {
289                 cb.sendResult(null);
290             } catch (RemoteException e) { }
291         }, callback));
292     }
293 
294     /**
295      * Set animation options for collecting transition by ActivityRecord.
296      * @param options AnimationOptions captured from ActivityOptions
297      */
setOverrideAnimation(TransitionInfo.AnimationOptions options, @Nullable IRemoteCallback startCallback, @Nullable IRemoteCallback finishCallback)298     void setOverrideAnimation(TransitionInfo.AnimationOptions options,
299             @Nullable IRemoteCallback startCallback, @Nullable IRemoteCallback finishCallback) {
300         if (mSyncId < 0) return;
301         mOverrideOptions = options;
302         sendRemoteCallback(mClientAnimationStartCallback);
303         mClientAnimationStartCallback = startCallback;
304         mClientAnimationFinishCallback = finishCallback;
305     }
306 
307     /**
308      * Call this when all known changes related to this transition have been applied. Until
309      * all participants have finished drawing, the transition can still collect participants.
310      *
311      * If this is called before the transition is started, it will be deferred until start.
312      *
313      * @param wc A reference point to determine which ready-group to update. For now, each display
314      *           has its own ready-group, so this is used to look-up which display to mark ready.
315      *           The transition will wait for all groups to be ready.
316      */
setReady(WindowContainer wc, boolean ready)317     void setReady(WindowContainer wc, boolean ready) {
318         if (mSyncId < 0) return;
319         mReadyTracker.setReadyFrom(wc, ready);
320         applyReady();
321     }
322 
applyReady()323     private void applyReady() {
324         if (mState < STATE_STARTED) return;
325         final boolean ready = mReadyTracker.allReady();
326         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
327                 "Set transition ready=%b %d", ready, mSyncId);
328         mSyncEngine.setReady(mSyncId, ready);
329     }
330 
331     /**
332      * Sets all possible ready groups to ready.
333      * @see ReadyTracker#setAllReady.
334      */
setAllReady()335     void setAllReady() {
336         if (mSyncId < 0) return;
337         mReadyTracker.setAllReady();
338         applyReady();
339     }
340 
341     @VisibleForTesting
allReady()342     boolean allReady() {
343         return mReadyTracker.allReady();
344     }
345 
346     /**
347      * Build a transaction that "resets" all the re-parenting and layer changes. This is
348      * intended to be applied at the end of the transition but before the finish callback. This
349      * needs to be passed/applied in shell because until finish is called, shell owns the surfaces.
350      * Additionally, this gives shell the ability to better deal with merged transitions.
351      */
buildFinishTransaction(SurfaceControl.Transaction t, SurfaceControl rootLeash)352     private void buildFinishTransaction(SurfaceControl.Transaction t, SurfaceControl rootLeash) {
353         final Point tmpPos = new Point();
354         // usually only size 1
355         final ArraySet<DisplayContent> displays = new ArraySet<>();
356         for (int i = mTargets.size() - 1; i >= 0; --i) {
357             final WindowContainer target = mTargets.valueAt(i);
358             if (target.getParent() != null) {
359                 final SurfaceControl targetLeash = getLeashSurface(target);
360                 final SurfaceControl origParent = getOrigParentSurface(target);
361                 // Ensure surfaceControls are re-parented back into the hierarchy.
362                 t.reparent(targetLeash, origParent);
363                 t.setLayer(targetLeash, target.getLastLayer());
364                 target.getRelativePosition(tmpPos);
365                 t.setPosition(targetLeash, tmpPos.x, tmpPos.y);
366                 t.setCornerRadius(targetLeash, 0);
367                 t.setShadowRadius(targetLeash, 0);
368                 displays.add(target.getDisplayContent());
369             }
370         }
371         // Need to update layers on involved displays since they were all paused while
372         // the animation played. This puts the layers back into the correct order.
373         for (int i = displays.size() - 1; i >= 0; --i) {
374             if (displays.valueAt(i) == null) continue;
375             displays.valueAt(i).assignChildLayers(t);
376         }
377         if (rootLeash.isValid()) {
378             t.reparent(rootLeash, null);
379         }
380     }
381 
382     /**
383      * The transition has finished animating and is ready to finalize WM state. This should not
384      * be called directly; use {@link TransitionController#finishTransition} instead.
385      */
finishTransition()386     void finishTransition() {
387         mStartTransaction = mFinishTransaction = null;
388         if (mState < STATE_PLAYING) {
389             throw new IllegalStateException("Can't finish a non-playing transition " + mSyncId);
390         }
391 
392         // Commit all going-invisible containers
393         boolean activitiesWentInvisible = false;
394         for (int i = 0; i < mParticipants.size(); ++i) {
395             final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord();
396             if (ar != null) {
397                 boolean visibleAtTransitionEnd = mVisibleAtTransitionEndTokens.contains(ar);
398                 // We need both the expected visibility AND current requested-visibility to be
399                 // false. If it is expected-visible but not currently visible, it means that
400                 // another animation is queued-up to animate this to invisibility, so we can't
401                 // remove the surfaces yet. If it is currently visible, but not expected-visible,
402                 // then doing commitVisibility here would actually be out-of-order and leave the
403                 // activity in a bad state.
404                 if (!visibleAtTransitionEnd && !ar.isVisibleRequested()) {
405                     boolean commitVisibility = true;
406                     if (ar.getDeferHidingClient() && ar.getTask() != null) {
407                         if (ar.pictureInPictureArgs != null
408                                 && ar.pictureInPictureArgs.isAutoEnterEnabled()) {
409                             mController.mAtm.enterPictureInPictureMode(ar, ar.pictureInPictureArgs);
410                             // Avoid commit visibility to false here, or else we will get a sudden
411                             // "flash" / surface going invisible for a split second.
412                             commitVisibility = false;
413                         } else {
414                             mController.mAtm.mTaskSupervisor.mUserLeaving = true;
415                             ar.getTaskFragment().startPausing(false /* uiSleeping */,
416                                     null /* resuming */, "finishTransition");
417                             mController.mAtm.mTaskSupervisor.mUserLeaving = false;
418                         }
419                     }
420                     if (commitVisibility) {
421                         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
422                                 "  Commit activity becoming invisible: %s", ar);
423                         final Task task = ar.getTask();
424                         if (task != null && !task.isVisibleRequested()
425                                 && mTransientLaunches != null) {
426                             // If transition is transient, then snapshots are taken at end of
427                             // transition.
428                             mController.mTaskSnapshotController.recordTaskSnapshot(
429                                     task, false /* allowSnapshotHome */);
430                         }
431                         ar.commitVisibility(false /* visible */, false /* performLayout */,
432                                 true /* fromTransition */);
433                         activitiesWentInvisible = true;
434                     }
435                 }
436                 if (mChanges.get(ar).mVisible != visibleAtTransitionEnd) {
437                     // Legacy dispatch relies on this (for now).
438                     ar.mEnteringAnimation = visibleAtTransitionEnd;
439                 }
440                 mController.dispatchLegacyAppTransitionFinished(ar);
441             }
442             final WallpaperWindowToken wt = mParticipants.valueAt(i).asWallpaperToken();
443             if (wt != null) {
444                 final boolean visibleAtTransitionEnd = mVisibleAtTransitionEndTokens.contains(wt);
445                 if (!visibleAtTransitionEnd && !wt.isVisibleRequested()) {
446                     ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
447                             "  Commit wallpaper becoming invisible: %s", wt);
448                     wt.commitVisibility(false /* visible */);
449                 }
450             }
451         }
452         if (activitiesWentInvisible) {
453             // Always schedule stop processing when transition finishes because activities don't
454             // stop while they are in a transition thus their stop could still be pending.
455             mController.mAtm.mTaskSupervisor
456                     .scheduleProcessStoppingAndFinishingActivitiesIfNeeded();
457         }
458 
459         sendRemoteCallback(mClientAnimationFinishCallback);
460 
461         legacyRestoreNavigationBarFromApp();
462 
463         if (mRecentsDisplayId != INVALID_DISPLAY) {
464             // Clean up input monitors (for recents)
465             final DisplayContent dc =
466                     mController.mAtm.mRootWindowContainer.getDisplayContent(mRecentsDisplayId);
467             dc.getInputMonitor().setActiveRecents(null /* activity */, null /* layer */);
468         }
469     }
470 
abort()471     void abort() {
472         // This calls back into itself via controller.abort, so just early return here.
473         if (mState == STATE_ABORT) return;
474         if (mState != STATE_COLLECTING) {
475             throw new IllegalStateException("Too late to abort.");
476         }
477         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Aborting Transition: %d", mSyncId);
478         mController.dispatchLegacyAppTransitionCancelled();
479         mState = STATE_ABORT;
480         // Syncengine abort will call through to onTransactionReady()
481         mSyncEngine.abort(mSyncId);
482     }
483 
setRemoteTransition(RemoteTransition remoteTransition)484     void setRemoteTransition(RemoteTransition remoteTransition) {
485         mRemoteTransition = remoteTransition;
486     }
487 
getRemoteTransition()488     RemoteTransition getRemoteTransition() {
489         return mRemoteTransition;
490     }
491 
492     @Override
onTransactionReady(int syncId, SurfaceControl.Transaction transaction)493     public void onTransactionReady(int syncId, SurfaceControl.Transaction transaction) {
494         if (syncId != mSyncId) {
495             Slog.e(TAG, "Unexpected Sync ID " + syncId + ". Expected " + mSyncId);
496             return;
497         }
498         int displayId = DEFAULT_DISPLAY;
499         for (WindowContainer container : mParticipants) {
500             if (container.mDisplayContent == null) continue;
501             displayId = container.mDisplayContent.getDisplayId();
502         }
503 
504         if (mState == STATE_ABORT) {
505             mController.abort(this);
506             mController.mAtm.mRootWindowContainer.getDisplayContent(displayId)
507                     .getPendingTransaction().merge(transaction);
508             mSyncId = -1;
509             mOverrideOptions = null;
510             return;
511         }
512 
513         mState = STATE_PLAYING;
514         mController.moveToPlaying(this);
515 
516         if (mController.mAtm.mTaskSupervisor.getKeyguardController().isKeyguardLocked()) {
517             mFlags |= TRANSIT_FLAG_KEYGUARD_LOCKED;
518         }
519 
520         // Resolve the animating targets from the participants
521         mTargets = calculateTargets(mParticipants, mChanges);
522         final TransitionInfo info = calculateTransitionInfo(mType, mFlags, mTargets, mChanges);
523         info.setAnimationOptions(mOverrideOptions);
524 
525         // TODO(b/188669821): Move to animation impl in shell.
526         handleLegacyRecentsStartBehavior(displayId, info);
527 
528         handleNonAppWindowsInTransition(displayId, mType, mFlags);
529 
530         reportStartReasonsToLogger();
531 
532         // The callback is only populated for custom activity-level client animations
533         sendRemoteCallback(mClientAnimationStartCallback);
534 
535         // Manually show any activities that are visibleRequested. This is needed to properly
536         // support simultaneous animation queueing/merging. Specifically, if transition A makes
537         // an activity invisible, it's finishTransaction (which is applied *after* the animation)
538         // will hide the activity surface. If transition B then makes the activity visible again,
539         // the normal surfaceplacement logic won't add a show to this start transaction because
540         // the activity visibility hasn't been committed yet. To deal with this, we have to manually
541         // show here in the same way that we manually hide in finishTransaction.
542         for (int i = mParticipants.size() - 1; i >= 0; --i) {
543             final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord();
544             if (ar == null || !ar.mVisibleRequested) continue;
545             transaction.show(ar.getSurfaceControl());
546 
547             // Also manually show any non-reported parents. This is necessary in a few cases
548             // where a task is NOT organized but had its visibility changed within its direct
549             // parent. An example of this is if an alternate home leaf-task HB is started atop the
550             // normal home leaf-task HA: these are both in the Home root-task HR, so there will be a
551             // transition containing HA and HB where HA surface is hidden. If a standard task SA is
552             // launched on top, then HB finishes, no transition will happen since neither home is
553             // visible. When SA finishes, the transition contains HR rather than HA. Since home
554             // leaf-tasks are NOT organized, HA won't be in the transition and thus its surface
555             // wouldn't be shown. Just show is safe here since all other properties will have
556             // already been reset by the original hiding-transition's finishTransaction (we can't
557             // show in the finishTransaction because by then the activity doesn't hide until
558             // surface placement).
559             for (WindowContainer p = ar.getParent(); p != null && !mTargets.contains(p);
560                     p = p.getParent()) {
561                 if (p.getSurfaceControl() != null) {
562                     transaction.show(p.getSurfaceControl());
563                 }
564             }
565         }
566 
567         // Record windowtokens (activity/wallpaper) that are expected to be visible after the
568         // transition animation. This will be used in finishTransition to prevent prematurely
569         // committing visibility.
570         for (int i = mParticipants.size() - 1; i >= 0; --i) {
571             final WindowContainer wc = mParticipants.valueAt(i);
572             if (wc.asWindowToken() == null || !wc.isVisibleRequested()) continue;
573             mVisibleAtTransitionEndTokens.add(wc.asWindowToken());
574         }
575 
576         // Take task snapshots before the animation so that we can capture IME before it gets
577         // transferred. If transition is transient, IME won't be moved during the transition and
578         // the tasks are still live, so we take the snapshot at the end of the transition instead.
579         if (mTransientLaunches == null) {
580             for (int i = mParticipants.size() - 1; i >= 0; --i) {
581                 final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord();
582                 if (ar == null || ar.isVisibleRequested() || ar.getTask() == null
583                         || ar.getTask().isVisibleRequested()) continue;
584                 mController.mTaskSnapshotController.recordTaskSnapshot(
585                         ar.getTask(), false /* allowSnapshotHome */);
586             }
587         }
588 
589         mStartTransaction = transaction;
590         mFinishTransaction = mController.mAtm.mWindowManager.mTransactionFactory.get();
591         buildFinishTransaction(mFinishTransaction, info.getRootLeash());
592         if (mController.getTransitionPlayer() != null) {
593             mController.dispatchLegacyAppTransitionStarting(info);
594             try {
595                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
596                         "Calling onTransitionReady: %s", info);
597                 mController.getTransitionPlayer().onTransitionReady(
598                         this, info, transaction, mFinishTransaction);
599             } catch (RemoteException e) {
600                 // If there's an exception when trying to send the mergedTransaction to the
601                 // client, we should finish and apply it here so the transactions aren't lost.
602                 cleanUpOnFailure();
603             }
604         } else {
605             // No player registered, so just finish/apply immediately
606             cleanUpOnFailure();
607         }
608         mSyncId = -1;
609         mOverrideOptions = null;
610     }
611 
612     /**
613      * If the remote failed for any reason, use this to do any appropriate clean-up. Do not call
614      * this directly, it's designed to by called by {@link TransitionController} only.
615      */
cleanUpOnFailure()616     void cleanUpOnFailure() {
617         // No need to clean-up if this isn't playing yet.
618         if (mState < STATE_PLAYING) return;
619 
620         if (mStartTransaction != null) {
621             mStartTransaction.apply();
622         }
623         if (mFinishTransaction != null) {
624             mFinishTransaction.apply();
625         }
626         mController.finishTransition(this);
627     }
628 
629     /** @see RecentsAnimationController#attachNavigationBarToApp */
handleLegacyRecentsStartBehavior(int displayId, TransitionInfo info)630     private void handleLegacyRecentsStartBehavior(int displayId, TransitionInfo info) {
631         if ((mFlags & TRANSIT_FLAG_IS_RECENTS) == 0) {
632             return;
633         }
634         final DisplayContent dc =
635                 mController.mAtm.mRootWindowContainer.getDisplayContent(displayId);
636         if (dc == null) return;
637         mRecentsDisplayId = displayId;
638 
639         // Recents has an input-consumer to grab input from the "live tile" app. Set that up here
640         final InputConsumerImpl recentsAnimationInputConsumer =
641                 dc.getInputMonitor().getInputConsumer(INPUT_CONSUMER_RECENTS_ANIMATION);
642         if (recentsAnimationInputConsumer != null) {
643             // find the top-most going-away activity and the recents activity. The top-most
644             // is used as layer reference while the recents is used for registering the consumer
645             // override.
646             ActivityRecord recentsActivity = null;
647             ActivityRecord topActivity = null;
648             for (int i = 0; i < info.getChanges().size(); ++i) {
649                 final TransitionInfo.Change change = info.getChanges().get(i);
650                 if (change.getTaskInfo() == null) continue;
651                 final Task task = Task.fromWindowContainerToken(
652                         info.getChanges().get(i).getTaskInfo().token);
653                 if (task == null) continue;
654                 final int activityType = change.getTaskInfo().topActivityType;
655                 final boolean isRecents = activityType == ACTIVITY_TYPE_HOME
656                         || activityType == ACTIVITY_TYPE_RECENTS;
657                 if (isRecents && recentsActivity == null) {
658                     recentsActivity = task.getTopVisibleActivity();
659                 } else if (!isRecents && topActivity == null) {
660                     topActivity = task.getTopNonFinishingActivity();
661                 }
662             }
663             if (recentsActivity != null && topActivity != null) {
664                 recentsAnimationInputConsumer.mWindowHandle.touchableRegion.set(
665                         topActivity.getBounds());
666                 dc.getInputMonitor().setActiveRecents(recentsActivity, topActivity);
667             }
668         }
669 
670         // The rest of this function handles nav-bar reparenting
671 
672         if (!dc.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition()
673                 // Skip the case where the nav bar is controlled by fade rotation.
674                 || dc.getFadeRotationAnimationController() != null) {
675             return;
676         }
677 
678         WindowContainer topWC = null;
679         // Find the top-most non-home, closing app.
680         for (int i = 0; i < info.getChanges().size(); ++i) {
681             final TransitionInfo.Change c = info.getChanges().get(i);
682             if (c.getTaskInfo() == null || c.getTaskInfo().displayId != displayId
683                     || c.getTaskInfo().getActivityType() != ACTIVITY_TYPE_STANDARD
684                     || !(c.getMode() == TRANSIT_CLOSE || c.getMode() == TRANSIT_TO_BACK)) {
685                 continue;
686             }
687             topWC = WindowContainer.fromBinder(c.getContainer().asBinder());
688             break;
689         }
690         if (topWC == null || topWC.inMultiWindowMode()) {
691             return;
692         }
693 
694         final WindowState navWindow = dc.getDisplayPolicy().getNavigationBar();
695         if (navWindow == null || navWindow.mToken == null) {
696             return;
697         }
698         mNavBarAttachedToApp = true;
699         navWindow.mToken.cancelAnimation();
700         final SurfaceControl.Transaction t = navWindow.mToken.getPendingTransaction();
701         final SurfaceControl navSurfaceControl = navWindow.mToken.getSurfaceControl();
702         t.reparent(navSurfaceControl, topWC.getSurfaceControl());
703         t.show(navSurfaceControl);
704 
705         final WindowContainer imeContainer = dc.getImeContainer();
706         if (imeContainer.isVisible()) {
707             t.setRelativeLayer(navSurfaceControl, imeContainer.getSurfaceControl(), 1);
708         } else {
709             // Place the nav bar on top of anything else in the top activity.
710             t.setLayer(navSurfaceControl, Integer.MAX_VALUE);
711         }
712         if (mController.mStatusBar != null) {
713             mController.mStatusBar.setNavigationBarLumaSamplingEnabled(displayId, false);
714         }
715     }
716 
717     /** @see RecentsAnimationController#restoreNavigationBarFromApp */
legacyRestoreNavigationBarFromApp()718     void legacyRestoreNavigationBarFromApp() {
719         if (!mNavBarAttachedToApp) return;
720         mNavBarAttachedToApp = false;
721 
722         if (mRecentsDisplayId == INVALID_DISPLAY) {
723             Slog.e(TAG, "Reparented navigation bar without a valid display");
724             mRecentsDisplayId = DEFAULT_DISPLAY;
725         }
726 
727         if (mController.mStatusBar != null) {
728             mController.mStatusBar.setNavigationBarLumaSamplingEnabled(mRecentsDisplayId, true);
729         }
730 
731         final DisplayContent dc =
732                 mController.mAtm.mRootWindowContainer.getDisplayContent(mRecentsDisplayId);
733         final WindowState navWindow = dc.getDisplayPolicy().getNavigationBar();
734         if (navWindow == null) return;
735         navWindow.setSurfaceTranslationY(0);
736 
737         final WindowToken navToken = navWindow.mToken;
738         if (navToken == null) return;
739         final SurfaceControl.Transaction t = dc.getPendingTransaction();
740         final WindowContainer parent = navToken.getParent();
741         t.setLayer(navToken.getSurfaceControl(), navToken.getLastLayer());
742 
743         boolean animate = false;
744         // Search for the home task. If it is supposed to be visible, then the navbar is not at
745         // the bottom of the screen, so we need to animate it.
746         for (int i = 0; i < mTargets.size(); ++i) {
747             final Task task = mTargets.valueAt(i).asTask();
748             if (task == null || !task.isHomeOrRecentsRootTask()) continue;
749             animate = task.isVisibleRequested();
750             break;
751         }
752 
753         if (animate) {
754             final NavBarFadeAnimationController controller =
755                     new NavBarFadeAnimationController(dc);
756             controller.fadeWindowToken(true);
757         } else {
758             // Reparent the SurfaceControl of nav bar token back.
759             t.reparent(navToken.getSurfaceControl(), parent.getSurfaceControl());
760         }
761     }
762 
handleNonAppWindowsInTransition(int displayId, @TransitionType int transit, @TransitionFlags int flags)763     private void handleNonAppWindowsInTransition(int displayId,
764             @TransitionType int transit, @TransitionFlags int flags) {
765         final DisplayContent dc =
766                 mController.mAtm.mRootWindowContainer.getDisplayContent(displayId);
767         if (dc == null) {
768             return;
769         }
770         if ((transit == TRANSIT_KEYGUARD_GOING_AWAY
771                 || (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0)
772                 && !WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation) {
773             if ((flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0
774                     && (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION) == 0
775                     && (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION) == 0) {
776                 Animation anim = mController.mAtm.mWindowManager.mPolicy
777                         .createKeyguardWallpaperExit(
778                                 (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE) != 0);
779                 if (anim != null) {
780                     anim.scaleCurrentDuration(
781                             mController.mAtm.mWindowManager.getTransitionAnimationScaleLocked());
782                     dc.mWallpaperController.startWallpaperAnimation(anim);
783                 }
784             }
785             dc.startKeyguardExitOnNonAppWindows(
786                     (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0,
787                     (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE) != 0,
788                     (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION) != 0);
789             if (!WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation) {
790                 // When remote animation is enabled for KEYGUARD_GOING_AWAY transition, SysUI
791                 // receives IRemoteAnimationRunner#onAnimationStart to start animation, so we don't
792                 // need to call IKeyguardService#keyguardGoingAway here.
793                 mController.mAtm.mWindowManager.mPolicy.startKeyguardExitAnimation(
794                         SystemClock.uptimeMillis(), 0 /* duration */);
795             }
796         }
797         if ((flags & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0) {
798             mController.mAtm.mWindowManager.mPolicy.applyKeyguardOcclusionChange(
799                     true /* keyguardOccludingStarted */);
800         }
801     }
802 
reportStartReasonsToLogger()803     private void reportStartReasonsToLogger() {
804         // Record transition start in metrics logger. We just assume everything is "DRAWN"
805         // at this point since splash-screen is a presentation (shell) detail.
806         ArrayMap<WindowContainer, Integer> reasons = new ArrayMap<>();
807         for (int i = mParticipants.size() - 1; i >= 0; --i) {
808             ActivityRecord r = mParticipants.valueAt(i).asActivityRecord();
809             if (r == null || !r.mVisibleRequested) continue;
810             // At this point, r is "ready", but if it's not "ALL ready" then it is probably only
811             // ready due to starting-window.
812             reasons.put(r, (r.mStartingData instanceof SplashScreenStartingData
813                     && !r.mLastAllReadyAtSync)
814                     ? APP_TRANSITION_SPLASH_SCREEN : APP_TRANSITION_WINDOWS_DRAWN);
815         }
816         mController.mAtm.mTaskSupervisor.getActivityMetricsLogger().notifyTransitionStarting(
817                 reasons);
818     }
819 
820     @Override
toString()821     public String toString() {
822         StringBuilder sb = new StringBuilder(64);
823         sb.append("TransitionRecord{");
824         sb.append(Integer.toHexString(System.identityHashCode(this)));
825         sb.append(" id=" + mSyncId);
826         sb.append(" type=" + transitTypeToString(mType));
827         sb.append(" flags=" + mFlags);
828         sb.append('}');
829         return sb.toString();
830     }
831 
reportIfNotTop(WindowContainer wc)832     private static boolean reportIfNotTop(WindowContainer wc) {
833         // Organized tasks need to be reported anyways because Core won't show() their surfaces
834         // and we can't rely on onTaskAppeared because it isn't in sync.
835         // Also report wallpaper so it can be handled properly during display change/rotation.
836         // TODO(shell-transitions): switch onTaskAppeared usage over to transitions OPEN.
837         return wc.isOrganized() || isWallpaper(wc);
838     }
839 
840     /** @return the depth of child within ancestor, 0 if child == ancestor, or -1 if not a child. */
getChildDepth(WindowContainer child, WindowContainer ancestor)841     private static int getChildDepth(WindowContainer child, WindowContainer ancestor) {
842         WindowContainer parent = child;
843         int depth = 0;
844         while (parent != null) {
845             if (parent == ancestor) {
846                 return depth;
847             }
848             parent = parent.getParent();
849             ++depth;
850         }
851         return -1;
852     }
853 
isWallpaper(WindowContainer wc)854     private static boolean isWallpaper(WindowContainer wc) {
855         return wc.asWallpaperToken() != null;
856     }
857 
occludesKeyguard(WindowContainer wc)858     private static boolean occludesKeyguard(WindowContainer wc) {
859         final ActivityRecord ar = wc.asActivityRecord();
860         if (ar != null) {
861             return ar.canShowWhenLocked();
862         }
863         final Task t = wc.asTask();
864         if (t != null) {
865             // Get the top activity which was visible (since this is going away, it will remain
866             // client visible until the transition is finished).
867             // skip hidden (or about to hide) apps
868             final ActivityRecord top = t.getActivity(WindowToken::isClientVisible);
869             return top != null && top.canShowWhenLocked();
870         }
871         return false;
872     }
873 
874     /**
875      * Under some conditions (eg. all visible targets within a parent container are transitioning
876      * the same way) the transition can be "promoted" to the parent container. This means an
877      * animation can play just on the parent rather than all the individual children.
878      *
879      * @return {@code true} if transition in target can be promoted to its parent.
880      */
canPromote(WindowContainer target, ArraySet<WindowContainer> topTargets, ArrayMap<WindowContainer, ChangeInfo> changes)881     private static boolean canPromote(WindowContainer target, ArraySet<WindowContainer> topTargets,
882             ArrayMap<WindowContainer, ChangeInfo> changes) {
883         final WindowContainer parent = target.getParent();
884         final ChangeInfo parentChanges = parent != null ? changes.get(parent) : null;
885         if (parent == null || !parent.canCreateRemoteAnimationTarget()
886                 || parentChanges == null || !parentChanges.hasChanged(parent)) {
887             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "      SKIP: %s",
888                     parent == null ? "no parent" : ("parent can't be target " + parent));
889             return false;
890         }
891         if (isWallpaper(target)) {
892             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "      SKIP: is wallpaper");
893             return false;
894         }
895         @TransitionInfo.TransitionMode int mode = TRANSIT_NONE;
896         // Go through all siblings of this target to see if any of them would prevent
897         // the target from promoting.
898         siblingLoop:
899         for (int i = parent.getChildCount() - 1; i >= 0; --i) {
900             final WindowContainer sibling = parent.getChildAt(i);
901             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "      check sibling %s",
902                     sibling);
903             // Check if any topTargets are the sibling or within it
904             for (int j = topTargets.size() - 1; j >= 0; --j) {
905                 final int depth = getChildDepth(topTargets.valueAt(j), sibling);
906                 if (depth < 0) continue;
907                 if (depth == 0) {
908                     final int siblingMode = changes.get(sibling).getTransitMode(sibling);
909                     ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
910                             "        sibling is a top target with mode %s",
911                             TransitionInfo.modeToString(siblingMode));
912                     if (mode == TRANSIT_NONE) {
913                         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
914                                 "          no common mode yet, so set it");
915                         mode = siblingMode;
916                     } else if (mode != siblingMode) {
917                         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
918                                 "          SKIP: common mode mismatch. was %s",
919                                 TransitionInfo.modeToString(mode));
920                         return false;
921                     }
922                     continue siblingLoop;
923                 } else {
924                     // Sibling subtree may not be promotable.
925                     ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
926                             "        SKIP: sibling contains top target %s",
927                             topTargets.valueAt(j));
928                     return false;
929                 }
930             }
931             // No other animations are playing in this sibling
932             if (sibling.isVisibleRequested()) {
933                 // Sibling is visible but not animating, so no promote.
934                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
935                         "        SKIP: sibling is visible but not part of transition");
936                 return false;
937             }
938         }
939         return true;
940     }
941 
942     /**
943      * Go through topTargets and try to promote (see {@link #canPromote}) one of them.
944      *
945      * @param topTargets set of just the top-most targets in the hierarchy of participants.
946      * @param targets all targets that will be sent to the player.
947      * @return {@code true} if something was promoted.
948      */
tryPromote(ArraySet<WindowContainer> topTargets, ArraySet<WindowContainer> targets, ArrayMap<WindowContainer, ChangeInfo> changes)949     private static boolean tryPromote(ArraySet<WindowContainer> topTargets,
950             ArraySet<WindowContainer> targets, ArrayMap<WindowContainer, ChangeInfo> changes) {
951         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "  --- Start combine pass ---");
952         // Go through each target until we find one that can be promoted.
953         for (WindowContainer targ : topTargets) {
954             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "    checking %s", targ);
955             if (!canPromote(targ, topTargets, changes)) {
956                 continue;
957             }
958             // No obstructions found to promotion, so promote
959             final WindowContainer parent = targ.getParent();
960             final ChangeInfo parentInfo = changes.get(parent);
961             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
962                     "      CAN PROMOTE: promoting to parent %s", parent);
963             targets.add(parent);
964 
965             // Go through all children of newly-promoted container and remove them from the
966             // top-targets.
967             for (int i = parent.getChildCount() - 1; i >= 0; --i) {
968                 final WindowContainer child = parent.getChildAt(i);
969                 int idx = targets.indexOf(child);
970                 if (idx >= 0) {
971                     final ChangeInfo childInfo = changes.get(child);
972                     if (reportIfNotTop(child)) {
973                         childInfo.mParent = parent;
974                         parentInfo.addChild(child);
975                         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
976                                 "        keep as target %s", child);
977                     } else {
978                         if (childInfo.mChildren != null) {
979                             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
980                                     "        merging children in from %s: %s", child,
981                                     childInfo.mChildren);
982                             parentInfo.addChildren(childInfo.mChildren);
983                         }
984                         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
985                                 "        remove from targets %s", child);
986                         targets.removeAt(idx);
987                     }
988                 }
989                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
990                         "        remove from topTargets %s", child);
991                 topTargets.remove(child);
992             }
993             topTargets.add(parent);
994             return true;
995         }
996         return false;
997     }
998 
999     /**
1000      * Find WindowContainers to be animated from a set of opening and closing apps. We will promote
1001      * animation targets to higher level in the window hierarchy if possible.
1002      */
1003     @VisibleForTesting
1004     @NonNull
calculateTargets(ArraySet<WindowContainer> participants, ArrayMap<WindowContainer, ChangeInfo> changes)1005     static ArraySet<WindowContainer> calculateTargets(ArraySet<WindowContainer> participants,
1006             ArrayMap<WindowContainer, ChangeInfo> changes) {
1007         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
1008                 "Start calculating TransitionInfo based on participants: %s", participants);
1009 
1010         final ArraySet<WindowContainer> topTargets = new ArraySet<>();
1011         // The final animation targets which cannot promote to higher level anymore.
1012         final ArraySet<WindowContainer> targets = new ArraySet<>();
1013 
1014         final ArrayList<WindowContainer> tmpList = new ArrayList<>();
1015 
1016         // Build initial set of top-level participants by removing any participants that are no-ops
1017         // or children of other participants or are otherwise invalid; however, keep around a list
1018         // of participants that should always be reported even if they aren't top.
1019         for (WindowContainer wc : participants) {
1020             // Don't include detached windows.
1021             if (!wc.isAttached()) {
1022                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
1023                         "  Rejecting as detached: %s", wc);
1024                 continue;
1025             }
1026 
1027             final ChangeInfo changeInfo = changes.get(wc);
1028 
1029             // Reject no-ops
1030             if (!changeInfo.hasChanged(wc)) {
1031                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
1032                         "  Rejecting as no-op: %s", wc);
1033                 continue;
1034             }
1035 
1036             // Search through ancestors to find the top-most participant (if one exists)
1037             WindowContainer topParent = null;
1038             tmpList.clear();
1039             if (reportIfNotTop(wc)) {
1040                 tmpList.add(wc);
1041             }
1042             // Wallpaper must be the top (regardless of how nested it is in DisplayAreas).
1043             boolean skipIntermediateReports = isWallpaper(wc);
1044             for (WindowContainer p = wc.getParent(); p != null; p = p.getParent()) {
1045                 if (!p.isAttached() || changes.get(p) == null || !changes.get(p).hasChanged(p)) {
1046                     // Again, we're skipping no-ops
1047                     break;
1048                 }
1049                 if (participants.contains(p)) {
1050                     topParent = p;
1051                     break;
1052                 } else if (isWallpaper(p)) {
1053                     skipIntermediateReports = true;
1054                 } else if (reportIfNotTop(p) && !skipIntermediateReports) {
1055                     tmpList.add(p);
1056                 }
1057             }
1058             if (topParent != null) {
1059                 // There was an ancestor participant, so don't add wc to targets unless always-
1060                 // report. Similarly, add any always-report parents along the way.
1061                 for (int i = 0; i < tmpList.size(); ++i) {
1062                     targets.add(tmpList.get(i));
1063                     final ChangeInfo info = changes.get(tmpList.get(i));
1064                     info.mParent = i < tmpList.size() - 1 ? tmpList.get(i + 1) : topParent;
1065                 }
1066                 continue;
1067             }
1068             // No ancestors in participant-list, so wc is a top target.
1069             targets.add(wc);
1070             topTargets.add(wc);
1071         }
1072 
1073         // Populate children lists
1074         for (int i = targets.size() - 1; i >= 0; --i) {
1075             if (changes.get(targets.valueAt(i)).mParent != null) {
1076                 changes.get(changes.get(targets.valueAt(i)).mParent).addChild(targets.valueAt(i));
1077             }
1078         }
1079 
1080         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "  Initial targets: %s", targets);
1081         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "  Top targets: %s", topTargets);
1082 
1083         // Combine targets by repeatedly going through the topTargets to see if they can be
1084         // promoted until there aren't any promotions possible.
1085         while (tryPromote(topTargets, targets, changes)) {
1086             // Empty on purpose
1087         }
1088         return targets;
1089     }
1090 
1091     /** Add any of `members` within `root` to `out` in top-to-bottom z-order. */
addMembersInOrder(WindowContainer root, ArraySet<WindowContainer> members, ArrayList<WindowContainer> out)1092     private static void addMembersInOrder(WindowContainer root, ArraySet<WindowContainer> members,
1093             ArrayList<WindowContainer> out) {
1094         for (int i = root.getChildCount() - 1; i >= 0; --i) {
1095             final WindowContainer child = root.getChildAt(i);
1096             addMembersInOrder(child, members, out);
1097             if (members.contains(child)) {
1098                 out.add(child);
1099             }
1100         }
1101     }
1102 
1103     /** Gets the leash surface for a window container */
getLeashSurface(WindowContainer wc)1104     private static SurfaceControl getLeashSurface(WindowContainer wc) {
1105         final DisplayContent asDC = wc.asDisplayContent();
1106         if (asDC != null) {
1107             // DisplayContent is the "root", so we use the windowing layer instead to avoid
1108             // hardware-screen-level surfaces.
1109             return asDC.getWindowingLayer();
1110         }
1111         return wc.getSurfaceControl();
1112     }
1113 
getOrigParentSurface(WindowContainer wc)1114     private static SurfaceControl getOrigParentSurface(WindowContainer wc) {
1115         if (wc.asDisplayContent() != null) {
1116             // DisplayContent is the "root", so we reinterpret it's wc as the window layer
1117             // making the parent surface the displaycontent's surface.
1118             return wc.getSurfaceControl();
1119         }
1120         return wc.getParent().getSurfaceControl();
1121     }
1122 
1123     /**
1124      * A ready group is defined by a root window-container where all transitioning windows under
1125      * it are expected to animate together as a group. At the moment, this treats each display as
1126      * a ready-group to match the existing legacy transition behavior.
1127      */
isReadyGroup(WindowContainer wc)1128     private static boolean isReadyGroup(WindowContainer wc) {
1129         return wc instanceof DisplayContent;
1130     }
1131 
1132     /**
1133      * Construct a TransitionInfo object from a set of targets and changes. Also populates the
1134      * root surface.
1135      */
1136     @VisibleForTesting
1137     @NonNull
calculateTransitionInfo(@ransitionType int type, int flags, ArraySet<WindowContainer> targets, ArrayMap<WindowContainer, ChangeInfo> changes)1138     static TransitionInfo calculateTransitionInfo(@TransitionType int type, int flags,
1139             ArraySet<WindowContainer> targets, ArrayMap<WindowContainer, ChangeInfo> changes) {
1140         final TransitionInfo out = new TransitionInfo(type, flags);
1141 
1142         final ArraySet<WindowContainer> appTargets = new ArraySet<>();
1143         final ArraySet<WindowContainer> wallpapers = new ArraySet<>();
1144         for (int i = targets.size() - 1; i >= 0; --i) {
1145             (isWallpaper(targets.valueAt(i)) ? wallpapers : appTargets).add(targets.valueAt(i));
1146         }
1147 
1148         // Find the top-most shared ancestor of app targets
1149         if (appTargets.isEmpty()) {
1150             out.setRootLeash(new SurfaceControl(), 0, 0);
1151             return out;
1152         }
1153         WindowContainer ancestor = appTargets.valueAt(appTargets.size() - 1).getParent();
1154 
1155         // Go up ancestor parent chain until all targets are descendants.
1156         ancestorLoop:
1157         while (ancestor != null) {
1158             for (int i = appTargets.size() - 1; i >= 0; --i) {
1159                 final WindowContainer wc = appTargets.valueAt(i);
1160                 if (!wc.isDescendantOf(ancestor)) {
1161                     ancestor = ancestor.getParent();
1162                     continue ancestorLoop;
1163                 }
1164             }
1165             break;
1166         }
1167 
1168         // Sort targets top-to-bottom in Z. Check ALL targets here in case the display area itself
1169         // is animating: then we want to include wallpapers at the right position.
1170         ArrayList<WindowContainer> sortedTargets = new ArrayList<>();
1171         addMembersInOrder(ancestor, targets, sortedTargets);
1172 
1173         // make leash based on highest (z-order) direct child of ancestor with a participant.
1174         WindowContainer leashReference = sortedTargets.get(0);
1175         while (leashReference.getParent() != ancestor) {
1176             leashReference = leashReference.getParent();
1177         }
1178         final SurfaceControl rootLeash = leashReference.makeAnimationLeash().setName(
1179                 "Transition Root: " + leashReference.getName()).build();
1180         SurfaceControl.Transaction t = ancestor.mWmService.mTransactionFactory.get();
1181         t.setLayer(rootLeash, leashReference.getLastLayer());
1182         t.apply();
1183         t.close();
1184         out.setRootLeash(rootLeash, ancestor.getBounds().left, ancestor.getBounds().top);
1185 
1186         // add the wallpapers at the bottom
1187         for (int i = wallpapers.size() - 1; i >= 0; --i) {
1188             final WindowContainer wc = wallpapers.valueAt(i);
1189             // If the displayarea itself is animating, then the wallpaper was already added.
1190             if (wc.isDescendantOf(ancestor)) break;
1191             sortedTargets.add(wc);
1192         }
1193 
1194         // Convert all the resolved ChangeInfos into TransactionInfo.Change objects in order.
1195         final int count = sortedTargets.size();
1196         for (int i = 0; i < count; ++i) {
1197             final WindowContainer target = sortedTargets.get(i);
1198             final ChangeInfo info = changes.get(target);
1199             final TransitionInfo.Change change = new TransitionInfo.Change(
1200                     target.mRemoteToken != null ? target.mRemoteToken.toWindowContainerToken()
1201                             : null, getLeashSurface(target));
1202             // TODO(shell-transitions): Use leash for non-organized windows.
1203             if (info.mParent != null) {
1204                 change.setParent(info.mParent.mRemoteToken.toWindowContainerToken());
1205             }
1206             change.setMode(info.getTransitMode(target));
1207             change.setStartAbsBounds(info.mAbsoluteBounds);
1208             change.setEndAbsBounds(target.getBounds());
1209             change.setEndRelOffset(target.getBounds().left - target.getParent().getBounds().left,
1210                     target.getBounds().top - target.getParent().getBounds().top);
1211             change.setFlags(info.getChangeFlags(target));
1212             change.setRotation(info.mRotation, target.getWindowConfiguration().getRotation());
1213             final Task task = target.asTask();
1214             if (task != null) {
1215                 final ActivityManager.RunningTaskInfo tinfo = new ActivityManager.RunningTaskInfo();
1216                 task.fillTaskInfo(tinfo);
1217                 change.setTaskInfo(tinfo);
1218                 change.setRotationAnimation(getTaskRotationAnimation(task));
1219                 final ActivityRecord topMostActivity = task.getTopMostActivity();
1220                 change.setAllowEnterPip(topMostActivity != null
1221                         && topMostActivity.checkEnterPictureInPictureAppOpsState());
1222             }
1223             out.addChange(change);
1224         }
1225 
1226         return out;
1227     }
1228 
getTaskRotationAnimation(@onNull Task task)1229     private static int getTaskRotationAnimation(@NonNull Task task) {
1230         final ActivityRecord top = task.getTopVisibleActivity();
1231         if (top == null) return ROTATION_ANIMATION_UNSPECIFIED;
1232         final WindowState mainWin = top.findMainWindow(false);
1233         if (mainWin == null) return ROTATION_ANIMATION_UNSPECIFIED;
1234         int anim = mainWin.getRotationAnimationHint();
1235         if (anim >= 0) return anim;
1236         anim = mainWin.getAttrs().rotationAnimation;
1237         if (anim != ROTATION_ANIMATION_SEAMLESS) return anim;
1238         if (mainWin != task.mDisplayContent.getDisplayPolicy().getTopFullscreenOpaqueWindow()
1239                 || !top.matchParentBounds()) {
1240             // At the moment, we only support seamless rotation if there is only one window showing.
1241             return ROTATION_ANIMATION_UNSPECIFIED;
1242         }
1243         return mainWin.getAttrs().rotationAnimation;
1244     }
1245 
getLegacyIsReady()1246     boolean getLegacyIsReady() {
1247         return mState == STATE_STARTED && mSyncId >= 0 && mSyncEngine.isReady(mSyncId);
1248     }
1249 
fromBinder(IBinder binder)1250     static Transition fromBinder(IBinder binder) {
1251         return (Transition) binder;
1252     }
1253 
1254     @VisibleForTesting
1255     static class ChangeInfo {
1256         // Usually "post" change state.
1257         WindowContainer mParent;
1258         ArraySet<WindowContainer> mChildren;
1259 
1260         // State tracking
1261         boolean mExistenceChanged = false;
1262         // before change state
1263         boolean mVisible;
1264         int mWindowingMode;
1265         final Rect mAbsoluteBounds = new Rect();
1266         boolean mShowWallpaper;
1267         int mRotation = ROTATION_UNDEFINED;
1268         @ActivityInfo.Config int mKnownConfigChanges;
1269 
ChangeInfo(@onNull WindowContainer origState)1270         ChangeInfo(@NonNull WindowContainer origState) {
1271             mVisible = origState.isVisibleRequested();
1272             mWindowingMode = origState.getWindowingMode();
1273             mAbsoluteBounds.set(origState.getBounds());
1274             mShowWallpaper = origState.showWallpaper();
1275             mRotation = origState.getWindowConfiguration().getRotation();
1276         }
1277 
1278         @VisibleForTesting
ChangeInfo(boolean visible, boolean existChange)1279         ChangeInfo(boolean visible, boolean existChange) {
1280             mVisible = visible;
1281             mExistenceChanged = existChange;
1282             mShowWallpaper = false;
1283         }
1284 
hasChanged(@onNull WindowContainer newState)1285         boolean hasChanged(@NonNull WindowContainer newState) {
1286             // If it's invisible and hasn't changed visibility, always return false since even if
1287             // something changed, it wouldn't be a visible change.
1288             final boolean currVisible = newState.isVisibleRequested();
1289             if (currVisible == mVisible && !mVisible) return false;
1290             return currVisible != mVisible
1291                     || mKnownConfigChanges != 0
1292                     // if mWindowingMode is 0, this container wasn't attached at collect time, so
1293                     // assume no change in windowing-mode.
1294                     || (mWindowingMode != 0 && newState.getWindowingMode() != mWindowingMode)
1295                     || !newState.getBounds().equals(mAbsoluteBounds)
1296                     || mRotation != newState.getWindowConfiguration().getRotation();
1297         }
1298 
1299         @TransitionInfo.TransitionMode
getTransitMode(@onNull WindowContainer wc)1300         int getTransitMode(@NonNull WindowContainer wc) {
1301             final boolean nowVisible = wc.isVisibleRequested();
1302             if (nowVisible == mVisible) {
1303                 return TRANSIT_CHANGE;
1304             }
1305             if (mExistenceChanged) {
1306                 return nowVisible ? TRANSIT_OPEN : TRANSIT_CLOSE;
1307             } else {
1308                 return nowVisible ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK;
1309             }
1310         }
1311 
1312         @TransitionInfo.ChangeFlags
getChangeFlags(@onNull WindowContainer wc)1313         int getChangeFlags(@NonNull WindowContainer wc) {
1314             int flags = 0;
1315             if (mShowWallpaper || wc.showWallpaper()) {
1316                 flags |= FLAG_SHOW_WALLPAPER;
1317             }
1318             if (!wc.fillsParent()) {
1319                 // TODO(b/172695805): hierarchical check. This is non-trivial because for containers
1320                 //                    it is effected by child visibility but needs to work even
1321                 //                    before visibility is committed. This means refactoring some
1322                 //                    checks to use requested visibility.
1323                 flags |= FLAG_TRANSLUCENT;
1324             }
1325             final Task task = wc.asTask();
1326             if (task != null && task.voiceSession != null) {
1327                 flags |= FLAG_IS_VOICE_INTERACTION;
1328             }
1329             final ActivityRecord record = wc.asActivityRecord();
1330             if (record != null) {
1331                 if (record.mUseTransferredAnimation) {
1332                     flags |= FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
1333                 }
1334                 if (record.mVoiceInteraction) {
1335                     flags |= FLAG_IS_VOICE_INTERACTION;
1336                 }
1337             }
1338             final DisplayContent dc = wc.asDisplayContent();
1339             if (dc != null) {
1340                 flags |= FLAG_IS_DISPLAY;
1341                 if (dc.hasAlertWindowSurfaces()) {
1342                     flags |= FLAG_DISPLAY_HAS_ALERT_WINDOWS;
1343                 }
1344             }
1345             if (isWallpaper(wc)) {
1346                 flags |= FLAG_IS_WALLPAPER;
1347             }
1348             if (occludesKeyguard(wc)) {
1349                 flags |= FLAG_OCCLUDES_KEYGUARD;
1350             }
1351             return flags;
1352         }
1353 
addChild(@onNull WindowContainer wc)1354         void addChild(@NonNull WindowContainer wc) {
1355             if (mChildren == null) {
1356                 mChildren = new ArraySet<>();
1357             }
1358             mChildren.add(wc);
1359         }
addChildren(@onNull ArraySet<WindowContainer> wcs)1360         void addChildren(@NonNull ArraySet<WindowContainer> wcs) {
1361             if (mChildren == null) {
1362                 mChildren = new ArraySet<>();
1363             }
1364             mChildren.addAll(wcs);
1365         }
1366     }
1367 
1368     /**
1369      * The transition sync mechanism has 2 parts:
1370      *   1. Whether all WM operations for a particular transition are "ready" (eg. did the app
1371      *      launch or stop or get a new configuration?).
1372      *   2. Whether all the windows involved have finished drawing their final-state content.
1373      *
1374      * A transition animation can play once both parts are complete. This ready-tracker keeps track
1375      * of part (1). Currently, WM code assumes that "readiness" (part 1) is grouped. This means that
1376      * even if the WM operations in one group are ready, the whole transition itself may not be
1377      * ready if there are WM operations still pending in another group. This class helps keep track
1378      * of readiness across the multiple groups. Currently, we assume that each display is a group
1379      * since that is how it has been until now.
1380      */
1381     private static class ReadyTracker {
1382         private final ArrayMap<WindowContainer, Boolean> mReadyGroups = new ArrayMap<>();
1383 
1384         /**
1385          * Ensures that this doesn't report as allReady before it has been used. This is needed
1386          * in very niche cases where a transition is a no-op (nothing has been collected) but we
1387          * still want to be marked ready (via. setAllReady).
1388          */
1389         private boolean mUsed = false;
1390 
1391         /**
1392          * If true, this overrides all ready groups and reports ready. Used by shell-initiated
1393          * transitions via {@link #setAllReady()}.
1394          */
1395         private boolean mReadyOverride = false;
1396 
1397         /**
1398          * Adds a ready-group. Any setReady calls in this subtree will be tracked together. For
1399          * now these are only DisplayContents.
1400          */
addGroup(WindowContainer wc)1401         void addGroup(WindowContainer wc) {
1402             if (mReadyGroups.containsKey(wc)) {
1403                 Slog.e(TAG, "Trying to add a ready-group twice: " + wc);
1404                 return;
1405             }
1406             mReadyGroups.put(wc, false);
1407         }
1408 
1409         /**
1410          * Sets a group's ready state.
1411          * @param wc Any container within a group's subtree. Used to identify the ready-group.
1412          */
setReadyFrom(WindowContainer wc, boolean ready)1413         void setReadyFrom(WindowContainer wc, boolean ready) {
1414             mUsed = true;
1415             WindowContainer current = wc;
1416             while (current != null) {
1417                 if (isReadyGroup(current)) {
1418                     mReadyGroups.put(current, ready);
1419                     ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Setting Ready-group to"
1420                             + " %b. group=%s from %s", ready, current, wc);
1421                     break;
1422                 }
1423                 current = current.getParent();
1424             }
1425         }
1426 
1427         /** Marks this as ready regardless of individual groups. */
setAllReady()1428         void setAllReady() {
1429             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Setting allReady override");
1430             mUsed = true;
1431             mReadyOverride = true;
1432         }
1433 
1434         /** @return true if all tracked subtrees are ready. */
allReady()1435         boolean allReady() {
1436             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " allReady query: used=%b "
1437                     + "override=%b states=[%s]", mUsed, mReadyOverride, groupsToString());
1438             if (!mUsed) return false;
1439             if (mReadyOverride) return true;
1440             for (int i = mReadyGroups.size() - 1; i >= 0; --i) {
1441                 final WindowContainer wc = mReadyGroups.keyAt(i);
1442                 if (!wc.isAttached() || !wc.isVisibleRequested()) continue;
1443                 if (!mReadyGroups.valueAt(i)) return false;
1444             }
1445             return true;
1446         }
1447 
groupsToString()1448         private String groupsToString() {
1449             StringBuilder b = new StringBuilder();
1450             for (int i = 0; i < mReadyGroups.size(); ++i) {
1451                 if (i != 0) b.append(',');
1452                 b.append(mReadyGroups.keyAt(i)).append(':')
1453                         .append(mReadyGroups.valueAt(i));
1454             }
1455             return b.toString();
1456         }
1457     }
1458 }
1459