1 /*
2  * Copyright (C) 2021 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.wm.shell.transition;
18 
19 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
20 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
21 import static android.view.WindowManager.TRANSIT_CHANGE;
22 import static android.view.WindowManager.TRANSIT_CLOSE;
23 import static android.view.WindowManager.TRANSIT_FIRST_CUSTOM;
24 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_OCCLUDING;
25 import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
26 import static android.view.WindowManager.TRANSIT_OPEN;
27 import static android.view.WindowManager.TRANSIT_SLEEP;
28 import static android.view.WindowManager.TRANSIT_TO_BACK;
29 import static android.view.WindowManager.TRANSIT_TO_FRONT;
30 import static android.view.WindowManager.fixScale;
31 import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
32 import static android.window.TransitionInfo.FLAG_IS_OCCLUDED;
33 import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
34 import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
35 import static android.window.TransitionInfo.FLAG_NO_ANIMATION;
36 import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
37 
38 import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
39 import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SHELL_TRANSITIONS;
40 import static com.android.wm.shell.util.TransitionUtil.isClosingType;
41 import static com.android.wm.shell.util.TransitionUtil.isOpeningType;
42 
43 import android.annotation.NonNull;
44 import android.annotation.Nullable;
45 import android.app.ActivityTaskManager;
46 import android.app.IApplicationThread;
47 import android.content.ContentResolver;
48 import android.content.Context;
49 import android.database.ContentObserver;
50 import android.os.Handler;
51 import android.os.IBinder;
52 import android.os.RemoteException;
53 import android.os.SystemProperties;
54 import android.provider.Settings;
55 import android.util.Log;
56 import android.util.Pair;
57 import android.view.SurfaceControl;
58 import android.view.WindowManager;
59 import android.window.ITransitionPlayer;
60 import android.window.RemoteTransition;
61 import android.window.TransitionFilter;
62 import android.window.TransitionInfo;
63 import android.window.TransitionMetrics;
64 import android.window.TransitionRequestInfo;
65 import android.window.WindowContainerTransaction;
66 import android.window.WindowOrganizer;
67 
68 import androidx.annotation.BinderThread;
69 
70 import com.android.internal.R;
71 import com.android.internal.annotations.VisibleForTesting;
72 import com.android.internal.protolog.common.ProtoLog;
73 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
74 import com.android.wm.shell.common.DisplayController;
75 import com.android.wm.shell.common.ExternalInterfaceBinder;
76 import com.android.wm.shell.common.RemoteCallable;
77 import com.android.wm.shell.common.ShellExecutor;
78 import com.android.wm.shell.common.TransactionPool;
79 import com.android.wm.shell.common.annotations.ExternalThread;
80 import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
81 import com.android.wm.shell.protolog.ShellProtoLogGroup;
82 import com.android.wm.shell.sysui.ShellCommandHandler;
83 import com.android.wm.shell.sysui.ShellController;
84 import com.android.wm.shell.sysui.ShellInit;
85 import com.android.wm.shell.util.TransitionUtil;
86 
87 import java.io.PrintWriter;
88 import java.util.ArrayList;
89 import java.util.Arrays;
90 
91 /**
92  * Plays transition animations. Within this player, each transition has a lifecycle.
93  * 1. When a transition is directly started or requested, it is added to "pending" state.
94  * 2. Once WMCore applies the transition and notifies, the transition moves to "ready" state.
95  * 3. When a transition starts animating, it is moved to the "active" state.
96  *
97  * Basically: --start--> PENDING --onTransitionReady--> READY --play--> ACTIVE --finish--> |
98  *                                                            --merge--> MERGED --^
99  *
100  * The READY and beyond lifecycle is managed per "track". Within a track, all the animations are
101  * serialized as described; however, multiple tracks can play simultaneously. This implies that,
102  * within a track, only one transition can be animating ("active") at a time.
103  *
104  * While a transition is animating in a track, transitions dispatched to the track will be queued
105  * in the "ready" state for their turn. At the same time, whenever a transition makes it to the
106  * head of the "ready" queue, it will attempt to merge to with the "active" transition. If the
107  * merge succeeds, it will be moved to the "active" transition's "merged" list and then the next
108  * "ready" transition can attempt to merge. Once the "active" transition animation is finished,
109  * the next "ready" transition can play.
110  *
111  * Track assignments are expected to be provided by WMCore and this generally tries to maintain
112  * the same assignments. If, however, WMCore decides that a transition conflicts with >1 active
113  * track, it will be marked as SYNC. This means that all currently active tracks must be flushed
114  * before the SYNC transition can play.
115  */
116 public class Transitions implements RemoteCallable<Transitions>,
117         ShellCommandHandler.ShellCommandActionHandler {
118     static final String TAG = "ShellTransitions";
119 
120     /** Set to {@code true} to enable shell transitions. */
121     public static final boolean ENABLE_SHELL_TRANSITIONS =
122             SystemProperties.getBoolean("persist.wm.debug.shell_transit", true);
123     public static final boolean SHELL_TRANSITIONS_ROTATION = ENABLE_SHELL_TRANSITIONS
124             && SystemProperties.getBoolean("persist.wm.debug.shell_transit_rotate", false);
125 
126     /** Transition type for exiting PIP via the Shell, via pressing the expand button. */
127     public static final int TRANSIT_EXIT_PIP = TRANSIT_FIRST_CUSTOM + 1;
128 
129     public static final int TRANSIT_EXIT_PIP_TO_SPLIT =  TRANSIT_FIRST_CUSTOM + 2;
130 
131     /** Transition type for removing PIP via the Shell, either via Dismiss bubble or Close. */
132     public static final int TRANSIT_REMOVE_PIP = TRANSIT_FIRST_CUSTOM + 3;
133 
134     /** Transition type for launching 2 tasks simultaneously. */
135     public static final int TRANSIT_SPLIT_SCREEN_PAIR_OPEN = TRANSIT_FIRST_CUSTOM + 4;
136 
137     /** Transition type for entering split by opening an app into side-stage. */
138     public static final int TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE = TRANSIT_FIRST_CUSTOM + 5;
139 
140     /** Transition type for dismissing split-screen via dragging the divider off the screen. */
141     public static final int TRANSIT_SPLIT_DISMISS_SNAP = TRANSIT_FIRST_CUSTOM + 6;
142 
143     /** Transition type for dismissing split-screen. */
144     public static final int TRANSIT_SPLIT_DISMISS = TRANSIT_FIRST_CUSTOM + 7;
145 
146     /** Transition type for freeform to maximize transition. */
147     public static final int TRANSIT_MAXIMIZE = WindowManager.TRANSIT_FIRST_CUSTOM + 8;
148 
149     /** Transition type for maximize to freeform transition. */
150     public static final int TRANSIT_RESTORE_FROM_MAXIMIZE = WindowManager.TRANSIT_FIRST_CUSTOM + 9;
151 
152     /** Transition type for starting the move to desktop mode. */
153     public static final int TRANSIT_START_DRAG_TO_DESKTOP_MODE =
154             WindowManager.TRANSIT_FIRST_CUSTOM + 10;
155 
156     /** Transition type for finalizing the move to desktop mode. */
157     public static final int TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE =
158             WindowManager.TRANSIT_FIRST_CUSTOM + 11;
159 
160     /** Transition type to fullscreen from desktop mode. */
161     public static final int TRANSIT_EXIT_DESKTOP_MODE = WindowManager.TRANSIT_FIRST_CUSTOM + 12;
162 
163     /** Transition type to animate back to fullscreen when drag to freeform is cancelled. */
164     public static final int TRANSIT_CANCEL_DRAG_TO_DESKTOP_MODE =
165             WindowManager.TRANSIT_FIRST_CUSTOM + 13;
166 
167     /** Transition type to animate the toggle resize between the max and default desktop sizes. */
168     public static final int TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE =
169             WindowManager.TRANSIT_FIRST_CUSTOM + 14;
170 
171     /** Transition to animate task to desktop. */
172     public static final int TRANSIT_MOVE_TO_DESKTOP = WindowManager.TRANSIT_FIRST_CUSTOM + 15;
173 
174     private final WindowOrganizer mOrganizer;
175     private final Context mContext;
176     private final ShellExecutor mMainExecutor;
177     private final ShellExecutor mAnimExecutor;
178     private final TransitionPlayerImpl mPlayerImpl;
179     private final DefaultTransitionHandler mDefaultTransitionHandler;
180     private final RemoteTransitionHandler mRemoteTransitionHandler;
181     private final DisplayController mDisplayController;
182     private final ShellController mShellController;
183     private final ShellTransitionImpl mImpl = new ShellTransitionImpl();
184     private final SleepHandler mSleepHandler = new SleepHandler();
185     private final Tracer mTracer = new Tracer();
186     private boolean mIsRegistered = false;
187 
188     /** List of possible handlers. Ordered by specificity (eg. tapped back to front). */
189     private final ArrayList<TransitionHandler> mHandlers = new ArrayList<>();
190 
191     @Nullable
192     private final ShellCommandHandler mShellCommandHandler;
193 
194     private final ArrayList<TransitionObserver> mObservers = new ArrayList<>();
195 
196     /** List of {@link Runnable} instances to run when the last active transition has finished.  */
197     private final ArrayList<Runnable> mRunWhenIdleQueue = new ArrayList<>();
198 
199     private float mTransitionAnimationScaleSetting = 1.0f;
200 
201     /**
202      * How much time we allow for an animation to finish itself on sync. If it takes longer, we
203      * will force-finish it (on this end) which may leave it in a bad state but won't hang the
204      * device. This needs to be pretty small because it is an allowance for each queued animation,
205      * however it can't be too small since there is some potential IPC involved.
206      */
207     private static final int SYNC_ALLOWANCE_MS = 120;
208 
209     /**
210      * Keyguard gets a more generous timeout to finish its animations, because we are always holding
211      * a sleep token during occlude/unocclude transitions and we want them to finish playing cleanly
212      */
213     private static final int SYNC_ALLOWANCE_KEYGUARD_MS = 2000;
214 
215     /** For testing only. Disables the force-finish timeout on sync. */
216     private boolean mDisableForceSync = false;
217 
218     private static final class ActiveTransition {
219         IBinder mToken;
220         TransitionHandler mHandler;
221         boolean mAborted;
222         TransitionInfo mInfo;
223         SurfaceControl.Transaction mStartT;
224         SurfaceControl.Transaction mFinishT;
225 
226         /** Ordered list of transitions which have been merged into this one. */
227         private ArrayList<ActiveTransition> mMerged;
228 
isSync()229         boolean isSync() {
230             return (mInfo.getFlags() & TransitionInfo.FLAG_SYNC) != 0;
231         }
232 
getTrack()233         int getTrack() {
234             return mInfo != null ? mInfo.getTrack() : -1;
235         }
236 
237         @Override
toString()238         public String toString() {
239             if (mInfo != null && mInfo.getDebugId() >= 0) {
240                 return "(#" + mInfo.getDebugId() + ")" + mToken + "@" + getTrack();
241             }
242             return mToken.toString() + "@" + getTrack();
243         }
244     }
245 
246     private static class Track {
247         /** Keeps track of transitions which are ready to play but still waiting for their turn. */
248         final ArrayList<ActiveTransition> mReadyTransitions = new ArrayList<>();
249 
250         /** The currently playing transition in this track. */
251         ActiveTransition mActiveTransition = null;
252 
isIdle()253         boolean isIdle() {
254             return mActiveTransition == null && mReadyTransitions.isEmpty();
255         }
256     }
257 
258     /** Keeps track of transitions which have been started, but aren't ready yet. */
259     private final ArrayList<ActiveTransition> mPendingTransitions = new ArrayList<>();
260 
261     /**
262      * Transitions which are ready to play, but haven't been sent to a track yet because a sync
263      * is ongoing.
264      */
265     private final ArrayList<ActiveTransition> mReadyDuringSync = new ArrayList<>();
266 
267     private final ArrayList<Track> mTracks = new ArrayList<>();
268 
Transitions(@onNull Context context, @NonNull ShellInit shellInit, @NonNull ShellController shellController, @NonNull WindowOrganizer organizer, @NonNull TransactionPool pool, @NonNull DisplayController displayController, @NonNull ShellExecutor mainExecutor, @NonNull Handler mainHandler, @NonNull ShellExecutor animExecutor)269     public Transitions(@NonNull Context context,
270             @NonNull ShellInit shellInit,
271             @NonNull ShellController shellController,
272             @NonNull WindowOrganizer organizer,
273             @NonNull TransactionPool pool,
274             @NonNull DisplayController displayController,
275             @NonNull ShellExecutor mainExecutor,
276             @NonNull Handler mainHandler,
277             @NonNull ShellExecutor animExecutor) {
278         this(context, shellInit, shellController, organizer, pool, displayController, mainExecutor,
279                 mainHandler, animExecutor, null,
280                 new RootTaskDisplayAreaOrganizer(mainExecutor, context));
281     }
282 
Transitions(@onNull Context context, @NonNull ShellInit shellInit, @NonNull ShellController shellController, @NonNull WindowOrganizer organizer, @NonNull TransactionPool pool, @NonNull DisplayController displayController, @NonNull ShellExecutor mainExecutor, @NonNull Handler mainHandler, @NonNull ShellExecutor animExecutor, @Nullable ShellCommandHandler shellCommandHandler, @NonNull RootTaskDisplayAreaOrganizer rootTDAOrganizer)283     public Transitions(@NonNull Context context,
284             @NonNull ShellInit shellInit,
285             @NonNull ShellController shellController,
286             @NonNull WindowOrganizer organizer,
287             @NonNull TransactionPool pool,
288             @NonNull DisplayController displayController,
289             @NonNull ShellExecutor mainExecutor,
290             @NonNull Handler mainHandler,
291             @NonNull ShellExecutor animExecutor,
292             @Nullable ShellCommandHandler shellCommandHandler,
293             @NonNull RootTaskDisplayAreaOrganizer rootTDAOrganizer) {
294         mOrganizer = organizer;
295         mContext = context;
296         mMainExecutor = mainExecutor;
297         mAnimExecutor = animExecutor;
298         mDisplayController = displayController;
299         mPlayerImpl = new TransitionPlayerImpl();
300         mDefaultTransitionHandler = new DefaultTransitionHandler(context, shellInit,
301                 displayController, pool, mainExecutor, mainHandler, animExecutor, rootTDAOrganizer);
302         mRemoteTransitionHandler = new RemoteTransitionHandler(mMainExecutor);
303         mShellController = shellController;
304         // The very last handler (0 in the list) should be the default one.
305         mHandlers.add(mDefaultTransitionHandler);
306         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Default");
307         // Next lowest priority is remote transitions.
308         mHandlers.add(mRemoteTransitionHandler);
309         mShellCommandHandler = shellCommandHandler;
310         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Remote");
311         shellInit.addInitCallback(this::onInit, this);
312     }
313 
onInit()314     private void onInit() {
315         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
316             mOrganizer.shareTransactionQueue();
317         }
318         mShellController.addExternalInterface(KEY_EXTRA_SHELL_SHELL_TRANSITIONS,
319                 this::createExternalInterface, this);
320 
321         ContentResolver resolver = mContext.getContentResolver();
322         mTransitionAnimationScaleSetting = getTransitionAnimationScaleSetting();
323         dispatchAnimScaleSetting(mTransitionAnimationScaleSetting);
324 
325         resolver.registerContentObserver(
326                 Settings.Global.getUriFor(Settings.Global.TRANSITION_ANIMATION_SCALE), false,
327                 new SettingsObserver());
328 
329         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
330             mIsRegistered = true;
331             // Register this transition handler with Core
332             try {
333                 mOrganizer.registerTransitionPlayer(mPlayerImpl);
334             } catch (RuntimeException e) {
335                 mIsRegistered = false;
336                 throw e;
337             }
338             // Pre-load the instance.
339             TransitionMetrics.getInstance();
340         }
341 
342         if (mShellCommandHandler != null) {
343             mShellCommandHandler.addCommandCallback("transitions", this, this);
344         }
345     }
346 
isRegistered()347     public boolean isRegistered() {
348         return mIsRegistered;
349     }
350 
getTransitionAnimationScaleSetting()351     private float getTransitionAnimationScaleSetting() {
352         return fixScale(Settings.Global.getFloat(mContext.getContentResolver(),
353                 Settings.Global.TRANSITION_ANIMATION_SCALE, mContext.getResources().getFloat(
354                                 R.dimen.config_appTransitionAnimationDurationScaleDefault)));
355     }
356 
asRemoteTransitions()357     public ShellTransitions asRemoteTransitions() {
358         return mImpl;
359     }
360 
createExternalInterface()361     private ExternalInterfaceBinder createExternalInterface() {
362         return new IShellTransitionsImpl(this);
363     }
364 
365     @Override
getContext()366     public Context getContext() {
367         return mContext;
368     }
369 
370     @Override
getRemoteCallExecutor()371     public ShellExecutor getRemoteCallExecutor() {
372         return mMainExecutor;
373     }
374 
dispatchAnimScaleSetting(float scale)375     private void dispatchAnimScaleSetting(float scale) {
376         for (int i = mHandlers.size() - 1; i >= 0; --i) {
377             mHandlers.get(i).setAnimScaleSetting(scale);
378         }
379     }
380 
381     /**
382      * Adds a handler candidate.
383      * @see TransitionHandler
384      */
addHandler(@onNull TransitionHandler handler)385     public void addHandler(@NonNull TransitionHandler handler) {
386         if (mHandlers.isEmpty()) {
387             throw new RuntimeException("Unexpected handler added prior to initialization, please "
388                     + "use ShellInit callbacks to ensure proper ordering");
389         }
390         mHandlers.add(handler);
391         // Set initial scale settings.
392         handler.setAnimScaleSetting(mTransitionAnimationScaleSetting);
393         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: %s",
394                 handler.getClass().getSimpleName());
395     }
396 
getMainExecutor()397     public ShellExecutor getMainExecutor() {
398         return mMainExecutor;
399     }
400 
getAnimExecutor()401     public ShellExecutor getAnimExecutor() {
402         return mAnimExecutor;
403     }
404 
405     /** Only use this in tests. This is used to avoid running animations during tests. */
406     @VisibleForTesting
replaceDefaultHandlerForTest(TransitionHandler handler)407     void replaceDefaultHandlerForTest(TransitionHandler handler) {
408         mHandlers.set(0, handler);
409     }
410 
411     /** Register a remote transition to be used when `filter` matches an incoming transition */
registerRemote(@onNull TransitionFilter filter, @NonNull RemoteTransition remoteTransition)412     public void registerRemote(@NonNull TransitionFilter filter,
413             @NonNull RemoteTransition remoteTransition) {
414         mRemoteTransitionHandler.addFiltered(filter, remoteTransition);
415     }
416 
417     /** Unregisters a remote transition and all associated filters */
unregisterRemote(@onNull RemoteTransition remoteTransition)418     public void unregisterRemote(@NonNull RemoteTransition remoteTransition) {
419         mRemoteTransitionHandler.removeFiltered(remoteTransition);
420     }
421 
getRemoteTransitionHandler()422     RemoteTransitionHandler getRemoteTransitionHandler() {
423         return mRemoteTransitionHandler;
424     }
425 
426     /** Registers an observer on the lifecycle of transitions. */
registerObserver(@onNull TransitionObserver observer)427     public void registerObserver(@NonNull TransitionObserver observer) {
428         mObservers.add(observer);
429     }
430 
431     /** Unregisters the observer. */
unregisterObserver(@onNull TransitionObserver observer)432     public void unregisterObserver(@NonNull TransitionObserver observer) {
433         mObservers.remove(observer);
434     }
435 
436     /** Boosts the process priority of remote animation player. */
setRunningRemoteTransitionDelegate(IApplicationThread appThread)437     public static void setRunningRemoteTransitionDelegate(IApplicationThread appThread) {
438         if (appThread == null) return;
439         try {
440             ActivityTaskManager.getService().setRunningRemoteTransitionDelegate(appThread);
441         } catch (SecurityException e) {
442             Log.e(TAG, "Unable to boost animation process. This should only happen"
443                     + " during unit tests");
444         } catch (RemoteException e) {
445             e.rethrowFromSystemServer();
446         }
447     }
448 
449     /**
450      * Runs the given {@code runnable} when the last active transition has finished, or immediately
451      * if there are currently no active transitions.
452      *
453      * <p>This method should be called on the Shell main-thread, where the given {@code runnable}
454      * will be executed when the last active transition is finished.
455      */
runOnIdle(Runnable runnable)456     public void runOnIdle(Runnable runnable) {
457         if (isIdle()) {
458             runnable.run();
459         } else {
460             mRunWhenIdleQueue.add(runnable);
461         }
462     }
463 
setDisableForceSyncForTest(boolean disable)464     void setDisableForceSyncForTest(boolean disable) {
465         mDisableForceSync = disable;
466     }
467 
468     /**
469      * Sets up visibility/alpha/transforms to resemble the starting state of an animation.
470      */
setupStartState(@onNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT)471     private static void setupStartState(@NonNull TransitionInfo info,
472             @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) {
473         boolean isOpening = isOpeningType(info.getType());
474         for (int i = info.getChanges().size() - 1; i >= 0; --i) {
475             final TransitionInfo.Change change = info.getChanges().get(i);
476             if (change.hasFlags(TransitionInfo.FLAGS_IS_NON_APP_WINDOW)) {
477                 // Currently system windows are controlled by WindowState, so don't change their
478                 // surfaces. Otherwise their surfaces could be hidden or cropped unexpectedly.
479                 // This includes Wallpaper (always z-ordered at bottom) and IME (associated with
480                 // app), because there may not be a transition associated with their visibility
481                 // changes, and currently they don't need transition animation.
482                 continue;
483             }
484             final SurfaceControl leash = change.getLeash();
485             final int mode = info.getChanges().get(i).getMode();
486 
487             if (mode == TRANSIT_TO_FRONT
488                     && ((change.getStartAbsBounds().height() != change.getEndAbsBounds().height()
489                     || change.getStartAbsBounds().width() != change.getEndAbsBounds().width()))) {
490                 // When the window is moved to front with a different size, make sure the crop is
491                 // updated to prevent it from using the old crop.
492                 t.setWindowCrop(leash, change.getEndAbsBounds().width(),
493                         change.getEndAbsBounds().height());
494             }
495 
496             // Don't move anything that isn't independent within its parents
497             if (!TransitionInfo.isIndependent(change, info)) {
498                 if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT || mode == TRANSIT_CHANGE) {
499                     t.show(leash);
500                     t.setMatrix(leash, 1, 0, 0, 1);
501                     t.setAlpha(leash, 1.f);
502                     t.setPosition(leash, change.getEndRelOffset().x, change.getEndRelOffset().y);
503                 }
504                 continue;
505             }
506 
507             if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
508                 t.show(leash);
509                 t.setMatrix(leash, 1, 0, 0, 1);
510                 if (isOpening
511                         // If this is a transferred starting window, we want it immediately visible.
512                         && (change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) == 0) {
513                     t.setAlpha(leash, 0.f);
514                 }
515                 finishT.show(leash);
516             } else if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) {
517                 finishT.hide(leash);
518             } else if (isOpening && mode == TRANSIT_CHANGE) {
519                 // Just in case there is a race with another animation (eg. recents finish()).
520                 // Changes are visible->visible so it's a problem if it isn't visible.
521                 t.show(leash);
522             }
523         }
524     }
525 
526     /**
527      * Reparents all participants into a shared parent and orders them based on: the global transit
528      * type, their transit mode, and their destination z-order.
529      */
setupAnimHierarchy(@onNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT)530     private static void setupAnimHierarchy(@NonNull TransitionInfo info,
531             @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) {
532         final int type = info.getType();
533         final boolean isOpening = isOpeningType(type);
534         final boolean isClosing = isClosingType(type);
535         for (int i = 0; i < info.getRootCount(); ++i) {
536             t.show(info.getRoot(i).getLeash());
537         }
538         final int numChanges = info.getChanges().size();
539         // Put animating stuff above this line and put static stuff below it.
540         final int zSplitLine = numChanges + 1;
541         // changes should be ordered top-to-bottom in z
542         for (int i = numChanges - 1; i >= 0; --i) {
543             final TransitionInfo.Change change = info.getChanges().get(i);
544             final SurfaceControl leash = change.getLeash();
545             final int mode = change.getMode();
546 
547             // Don't reparent anything that isn't independent within its parents
548             if (!TransitionInfo.isIndependent(change, info)) {
549                 continue;
550             }
551 
552             boolean hasParent = change.getParent() != null;
553 
554             final int rootIdx = TransitionUtil.rootIndexFor(change, info);
555             if (!hasParent) {
556                 t.reparent(leash, info.getRoot(rootIdx).getLeash());
557                 t.setPosition(leash,
558                         change.getStartAbsBounds().left - info.getRoot(rootIdx).getOffset().x,
559                         change.getStartAbsBounds().top - info.getRoot(rootIdx).getOffset().y);
560             }
561             final int layer;
562             // Put all the OPEN/SHOW on top
563             if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) {
564                 // Wallpaper is always at the bottom, opening wallpaper on top of closing one.
565                 if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
566                     layer = -zSplitLine + numChanges - i;
567                 } else {
568                     layer = -zSplitLine - i;
569                 }
570             } else if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
571                 if (isOpening
572                         // This is for when an activity launches while a different transition is
573                         // collecting.
574                         || change.hasFlags(FLAG_MOVED_TO_TOP)) {
575                     // put on top
576                     layer = zSplitLine + numChanges - i;
577                 } else {
578                     // put on bottom
579                     layer = zSplitLine - i;
580                 }
581             } else if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) {
582                 if (isOpening) {
583                     // put on bottom and leave visible
584                     layer = zSplitLine - i;
585                 } else {
586                     // put on top
587                     layer = zSplitLine + numChanges - i;
588                 }
589             } else { // CHANGE or other
590                 if (isClosing || TransitionUtil.isOrderOnly(change)) {
591                     // Put below CLOSE mode (in the "static" section).
592                     layer = zSplitLine - i;
593                 } else {
594                     // Put above CLOSE mode.
595                     layer = zSplitLine + numChanges - i;
596                 }
597             }
598             t.setLayer(leash, layer);
599         }
600     }
601 
findByToken(ArrayList<ActiveTransition> list, IBinder token)602     private static int findByToken(ArrayList<ActiveTransition> list, IBinder token) {
603         for (int i = list.size() - 1; i >= 0; --i) {
604             if (list.get(i).mToken == token) return i;
605         }
606         return -1;
607     }
608 
609     /**
610      * Look through a transition and see if all non-closing changes are no-animation. If so, no
611      * animation should play.
612      */
isAllNoAnimation(TransitionInfo info)613     static boolean isAllNoAnimation(TransitionInfo info) {
614         if (isClosingType(info.getType())) {
615             // no-animation is only relevant for launching (open) activities.
616             return false;
617         }
618         boolean hasNoAnimation = false;
619         final int changeSize = info.getChanges().size();
620         for (int i = changeSize - 1; i >= 0; --i) {
621             final TransitionInfo.Change change = info.getChanges().get(i);
622             if (isClosingType(change.getMode())) {
623                 // ignore closing apps since they are a side-effect of the transition and don't
624                 // animate.
625                 continue;
626             }
627             if (change.hasFlags(FLAG_NO_ANIMATION)) {
628                 hasNoAnimation = true;
629             } else {
630                 // at-least one relevant participant *is* animated, so we need to animate.
631                 return false;
632             }
633         }
634         return hasNoAnimation;
635     }
636 
637     /**
638      * Check if all changes in this transition are only ordering changes. If so, we won't animate.
639      */
isAllOrderOnly(TransitionInfo info)640     static boolean isAllOrderOnly(TransitionInfo info) {
641         for (int i = info.getChanges().size() - 1; i >= 0; --i) {
642             if (!TransitionUtil.isOrderOnly(info.getChanges().get(i))) return false;
643         }
644         return true;
645     }
646 
getOrCreateTrack(int trackId)647     private Track getOrCreateTrack(int trackId) {
648         while (trackId >= mTracks.size()) {
649             mTracks.add(new Track());
650         }
651         return mTracks.get(trackId);
652     }
653 
654     @VisibleForTesting
onTransitionReady(@onNull IBinder transitionToken, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT)655     void onTransitionReady(@NonNull IBinder transitionToken, @NonNull TransitionInfo info,
656             @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) {
657         info.setUnreleasedWarningCallSiteForAllSurfaces("Transitions.onTransitionReady");
658         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady %s: %s",
659                 transitionToken, info);
660         final int activeIdx = findByToken(mPendingTransitions, transitionToken);
661         if (activeIdx < 0) {
662             throw new IllegalStateException("Got transitionReady for non-pending transition "
663                     + transitionToken + ". expecting one of "
664                     + Arrays.toString(mPendingTransitions.stream().map(
665                             activeTransition -> activeTransition.mToken).toArray()));
666         }
667         // Move from pending to ready
668         final ActiveTransition active = mPendingTransitions.remove(activeIdx);
669         active.mInfo = info;
670         active.mStartT = t;
671         active.mFinishT = finishT;
672         if (activeIdx > 0) {
673             Log.i(TAG, "Transition might be ready out-of-order " + activeIdx + " for " + active
674                     + ". This is ok if it's on a different track.");
675         }
676         if (!mReadyDuringSync.isEmpty()) {
677             mReadyDuringSync.add(active);
678         } else {
679             dispatchReady(active);
680         }
681     }
682 
683     /**
684      * Returns true if dispatching succeeded, otherwise false. Dispatching can fail if it is
685      * blocked by a sync or sleep.
686      */
dispatchReady(ActiveTransition active)687     boolean dispatchReady(ActiveTransition active) {
688         final TransitionInfo info = active.mInfo;
689 
690         if (info.getType() == TRANSIT_SLEEP || active.isSync()) {
691             // Adding to *front*! If we are here, it means that it was pulled off the front
692             // so we are just putting it back; or, it is the first one so it doesn't matter.
693             mReadyDuringSync.add(0, active);
694             boolean hadPreceding = false;
695             // Now flush all the tracks.
696             for (int i = 0; i < mTracks.size(); ++i) {
697                 final Track tr = mTracks.get(i);
698                 if (tr.isIdle()) continue;
699                 hadPreceding = true;
700                 // Sleep starts a process of forcing all prior transitions to finish immediately
701                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
702                         "Start finish-for-sync track %d", i);
703                 finishForSync(active, i, null /* forceFinish */);
704             }
705             if (hadPreceding) {
706                 return false;
707             }
708             // Actually able to process the sleep now, so re-remove it from the queue and continue
709             // the normal flow.
710             mReadyDuringSync.remove(active);
711         }
712 
713         final Track track = getOrCreateTrack(info.getTrack());
714         track.mReadyTransitions.add(active);
715 
716         for (int i = 0; i < mObservers.size(); ++i) {
717             mObservers.get(i).onTransitionReady(
718                     active.mToken, info, active.mStartT, active.mFinishT);
719         }
720 
721         /*
722          * Some transitions we always need to report to keyguard even if they are empty.
723          * TODO (b/274954192): Remove this once keyguard dispatching fully moves to Shell.
724          */
725         if (info.getRootCount() == 0 && !KeyguardTransitionHandler.handles(info)) {
726             // No root-leashes implies that the transition is empty/no-op, so just do
727             // housekeeping and return.
728             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "No transition roots in %s so"
729                     + " abort", active);
730             onAbort(active);
731             return true;
732         }
733 
734         final int changeSize = info.getChanges().size();
735         boolean taskChange = false;
736         boolean transferStartingWindow = false;
737         int noAnimationBehindStartingWindow = 0;
738         boolean allOccluded = changeSize > 0;
739         for (int i = changeSize - 1; i >= 0; --i) {
740             final TransitionInfo.Change change = info.getChanges().get(i);
741             taskChange |= change.getTaskInfo() != null;
742             transferStartingWindow |= change.hasFlags(FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT);
743             if (change.hasAllFlags(FLAG_IS_BEHIND_STARTING_WINDOW | FLAG_NO_ANIMATION)) {
744                 noAnimationBehindStartingWindow++;
745             }
746             if (!change.hasFlags(FLAG_IS_OCCLUDED)) {
747                 allOccluded = false;
748             }
749         }
750         // There does not need animation when:
751         // A. Transfer starting window. Apply transfer starting window directly if there is no other
752         // task change. Since this is an activity->activity situation, we can detect it by selecting
753         // transitions with only 2 changes where
754         // 1. neither are tasks, and
755         // 2. one is a starting-window recipient, or all change is behind starting window.
756         if (!taskChange && (transferStartingWindow || noAnimationBehindStartingWindow == changeSize)
757                 && changeSize == 2
758                 // B. It's visibility change if the TRANSIT_TO_BACK/TO_FRONT happened when all
759                 // changes are underneath another change.
760                 || ((info.getType() == TRANSIT_TO_BACK || info.getType() == TRANSIT_TO_FRONT)
761                 && allOccluded)) {
762             // Treat this as an abort since we are bypassing any merge logic and effectively
763             // finishing immediately.
764             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
765                     "Non-visible anim so abort: %s", active);
766             onAbort(active);
767             return true;
768         }
769 
770         setupStartState(active.mInfo, active.mStartT, active.mFinishT);
771 
772         if (track.mReadyTransitions.size() > 1) {
773             // There are already transitions waiting in the queue, so just return.
774             return true;
775         }
776         processReadyQueue(track);
777         return true;
778     }
779 
areTracksIdle()780     private boolean areTracksIdle() {
781         for (int i = 0; i < mTracks.size(); ++i) {
782             if (!mTracks.get(i).isIdle()) return false;
783         }
784         return true;
785     }
786 
isAnimating()787     private boolean isAnimating() {
788         return !mReadyDuringSync.isEmpty() || !areTracksIdle();
789     }
790 
isIdle()791     private boolean isIdle() {
792         return mPendingTransitions.isEmpty() && !isAnimating();
793     }
794 
processReadyQueue(Track track)795     void processReadyQueue(Track track) {
796         if (track.mReadyTransitions.isEmpty()) {
797             if (track.mActiveTransition == null) {
798                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Track %d became idle",
799                         mTracks.indexOf(track));
800                 if (areTracksIdle()) {
801                     if (!mReadyDuringSync.isEmpty()) {
802                         // Dispatch everything unless we hit another sync
803                         while (!mReadyDuringSync.isEmpty()) {
804                             ActiveTransition next = mReadyDuringSync.remove(0);
805                             boolean success = dispatchReady(next);
806                             // Hit a sync or sleep, so stop dispatching.
807                             if (!success) break;
808                         }
809                     } else if (mPendingTransitions.isEmpty()) {
810                         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "All active transition "
811                                 + "animations finished");
812                         // Run all runnables from the run-when-idle queue.
813                         for (int i = 0; i < mRunWhenIdleQueue.size(); i++) {
814                             mRunWhenIdleQueue.get(i).run();
815                         }
816                         mRunWhenIdleQueue.clear();
817                     }
818                 }
819             }
820             return;
821         }
822         final ActiveTransition ready = track.mReadyTransitions.get(0);
823         if (track.mActiveTransition == null) {
824             // The normal case, just play it.
825             track.mReadyTransitions.remove(0);
826             track.mActiveTransition = ready;
827             if (ready.mAborted) {
828                 if (ready.mStartT != null) {
829                     ready.mStartT.apply();
830                 }
831                 // finish now since there's nothing to animate. Calls back into processReadyQueue
832                 onFinish(ready, null);
833                 return;
834             }
835             playTransition(ready);
836             // Attempt to merge any more queued-up transitions.
837             processReadyQueue(track);
838             return;
839         }
840         // An existing animation is playing, so see if we can merge.
841         final ActiveTransition playing = track.mActiveTransition;
842         if (ready.mAborted) {
843             // record as merged since it is no-op. Calls back into processReadyQueue
844             onMerged(playing, ready);
845             return;
846         }
847         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition %s ready while"
848                 + " %s is still animating. Notify the animating transition"
849                 + " in case they can be merged", ready, playing);
850         mTracer.logMergeRequested(ready.mInfo.getDebugId(), playing.mInfo.getDebugId());
851         playing.mHandler.mergeAnimation(ready.mToken, ready.mInfo, ready.mStartT,
852                 playing.mToken, (wct) -> onMerged(playing, ready));
853     }
854 
onMerged(@onNull ActiveTransition playing, @NonNull ActiveTransition merged)855     private void onMerged(@NonNull ActiveTransition playing, @NonNull ActiveTransition merged) {
856         if (playing.getTrack() != merged.getTrack()) {
857             throw new IllegalStateException("Can't merge across tracks: " + merged + " into "
858                     + playing);
859         }
860         final Track track = mTracks.get(playing.getTrack());
861         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition was merged: %s into %s",
862                 merged, playing);
863         int readyIdx = 0;
864         if (track.mReadyTransitions.isEmpty() || track.mReadyTransitions.get(0) != merged) {
865             Log.e(TAG, "Merged transition out-of-order? " + merged);
866             readyIdx = track.mReadyTransitions.indexOf(merged);
867             if (readyIdx < 0) {
868                 Log.e(TAG, "Merged a transition that is no-longer queued? " + merged);
869                 return;
870             }
871         }
872         track.mReadyTransitions.remove(readyIdx);
873         if (playing.mMerged == null) {
874             playing.mMerged = new ArrayList<>();
875         }
876         playing.mMerged.add(merged);
877         // if it was aborted, then onConsumed has already been reported.
878         if (merged.mHandler != null && !merged.mAborted) {
879             merged.mHandler.onTransitionConsumed(merged.mToken, false /* abort */, merged.mFinishT);
880         }
881         for (int i = 0; i < mObservers.size(); ++i) {
882             mObservers.get(i).onTransitionMerged(merged.mToken, playing.mToken);
883         }
884         mTracer.logMerged(merged.mInfo.getDebugId(), playing.mInfo.getDebugId());
885         // See if we should merge another transition.
886         processReadyQueue(track);
887     }
888 
playTransition(@onNull ActiveTransition active)889     private void playTransition(@NonNull ActiveTransition active) {
890         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Playing animation for %s", active);
891         for (int i = 0; i < mObservers.size(); ++i) {
892             mObservers.get(i).onTransitionStarting(active.mToken);
893         }
894 
895         setupAnimHierarchy(active.mInfo, active.mStartT, active.mFinishT);
896 
897         // If a handler already chose to run this animation, try delegating to it first.
898         if (active.mHandler != null) {
899             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " try firstHandler %s",
900                     active.mHandler);
901             boolean consumed = active.mHandler.startAnimation(active.mToken, active.mInfo,
902                     active.mStartT, active.mFinishT, (wct) -> onFinish(active, wct));
903             if (consumed) {
904                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by firstHandler");
905                 mTracer.logDispatched(active.mInfo.getDebugId(), active.mHandler);
906                 return;
907             }
908         }
909         // Otherwise give every other handler a chance
910         active.mHandler = dispatchTransition(active.mToken, active.mInfo, active.mStartT,
911                 active.mFinishT, (wct) -> onFinish(active, wct), active.mHandler);
912     }
913 
914     /**
915      * Gives every handler (in order) a chance to animate until one consumes the transition.
916      * @return the handler which consumed the transition.
917      */
dispatchTransition(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startT, @NonNull SurfaceControl.Transaction finishT, @NonNull TransitionFinishCallback finishCB, @Nullable TransitionHandler skip)918     TransitionHandler dispatchTransition(@NonNull IBinder transition, @NonNull TransitionInfo info,
919             @NonNull SurfaceControl.Transaction startT, @NonNull SurfaceControl.Transaction finishT,
920             @NonNull TransitionFinishCallback finishCB, @Nullable TransitionHandler skip) {
921         for (int i = mHandlers.size() - 1; i >= 0; --i) {
922             if (mHandlers.get(i) == skip) continue;
923             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " try handler %s",
924                     mHandlers.get(i));
925             boolean consumed = mHandlers.get(i).startAnimation(transition, info, startT, finishT,
926                     finishCB);
927             if (consumed) {
928                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by %s",
929                         mHandlers.get(i));
930                 mTracer.logDispatched(info.getDebugId(), mHandlers.get(i));
931                 return mHandlers.get(i);
932             }
933         }
934         throw new IllegalStateException(
935                 "This shouldn't happen, maybe the default handler is broken.");
936     }
937 
938     /**
939      * Gives every handler (in order) a chance to handle request until one consumes the transition.
940      * @return the WindowContainerTransaction given by the handler which consumed the transition.
941      */
dispatchRequest( @onNull IBinder transition, @NonNull TransitionRequestInfo request, @Nullable TransitionHandler skip)942     public Pair<TransitionHandler, WindowContainerTransaction> dispatchRequest(
943             @NonNull IBinder transition, @NonNull TransitionRequestInfo request,
944             @Nullable TransitionHandler skip) {
945         for (int i = mHandlers.size() - 1; i >= 0; --i) {
946             if (mHandlers.get(i) == skip) continue;
947             WindowContainerTransaction wct = mHandlers.get(i).handleRequest(transition, request);
948             if (wct != null) {
949                 return new Pair<>(mHandlers.get(i), wct);
950             }
951         }
952         return null;
953     }
954 
955     /** Aborts a transition. This will still queue it up to maintain order. */
onAbort(ActiveTransition transition)956     private void onAbort(ActiveTransition transition) {
957         final Track track = mTracks.get(transition.getTrack());
958         transition.mAborted = true;
959 
960         mTracer.logAborted(transition.mInfo.getDebugId());
961 
962         if (transition.mHandler != null) {
963             // Notifies to clean-up the aborted transition.
964             transition.mHandler.onTransitionConsumed(
965                     transition.mToken, true /* aborted */, null /* finishTransaction */);
966         }
967 
968         releaseSurfaces(transition.mInfo);
969 
970         // This still went into the queue (to maintain the correct finish ordering).
971         if (track.mReadyTransitions.size() > 1) {
972             // There are already transitions waiting in the queue, so just return.
973             return;
974         }
975         processReadyQueue(track);
976     }
977 
978     /**
979      * Releases an info's animation-surfaces. These don't need to persist and we need to release
980      * them asap so that SF can free memory sooner.
981      */
releaseSurfaces(@ullable TransitionInfo info)982     private void releaseSurfaces(@Nullable TransitionInfo info) {
983         if (info == null) return;
984         info.releaseAnimSurfaces();
985     }
986 
onFinish(ActiveTransition active, @Nullable WindowContainerTransaction wct)987     private void onFinish(ActiveTransition active,
988             @Nullable WindowContainerTransaction wct) {
989         final Track track = mTracks.get(active.getTrack());
990         if (track.mActiveTransition != active) {
991             Log.e(TAG, "Trying to finish a non-running transition. Either remote crashed or "
992                     + " a handler didn't properly deal with a merge. " + active,
993                     new RuntimeException());
994             return;
995         }
996         track.mActiveTransition = null;
997 
998         for (int i = 0; i < mObservers.size(); ++i) {
999             mObservers.get(i).onTransitionFinished(active.mToken, active.mAborted);
1000         }
1001         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition animation finished "
1002                 + "(aborted=%b), notifying core %s", active.mAborted, active);
1003         if (active.mStartT != null) {
1004             // Applied by now, so clear immediately to remove any references. Do not set to null
1005             // yet, though, since nullness is used later to disambiguate malformed transitions.
1006             active.mStartT.clear();
1007         }
1008         // Merge all associated transactions together
1009         SurfaceControl.Transaction fullFinish = active.mFinishT;
1010         if (active.mMerged != null) {
1011             for (int iM = 0; iM < active.mMerged.size(); ++iM) {
1012                 final ActiveTransition toMerge = active.mMerged.get(iM);
1013                 // Include start. It will be a no-op if it was already applied. Otherwise, we need
1014                 // it to maintain consistent state.
1015                 if (toMerge.mStartT != null) {
1016                     if (fullFinish == null) {
1017                         fullFinish = toMerge.mStartT;
1018                     } else {
1019                         fullFinish.merge(toMerge.mStartT);
1020                     }
1021                 }
1022                 if (toMerge.mFinishT != null) {
1023                     if (fullFinish == null) {
1024                         fullFinish = toMerge.mFinishT;
1025                     } else {
1026                         fullFinish.merge(toMerge.mFinishT);
1027                     }
1028                 }
1029             }
1030         }
1031         if (fullFinish != null) {
1032             fullFinish.apply();
1033         }
1034         // Now perform all the finish callbacks (starting with the playing one and then all the
1035         // transitions merged into it).
1036         releaseSurfaces(active.mInfo);
1037         mOrganizer.finishTransition(active.mToken, wct);
1038         if (active.mMerged != null) {
1039             for (int iM = 0; iM < active.mMerged.size(); ++iM) {
1040                 ActiveTransition merged = active.mMerged.get(iM);
1041                 mOrganizer.finishTransition(merged.mToken, null /* wct */);
1042                 releaseSurfaces(merged.mInfo);
1043             }
1044             active.mMerged.clear();
1045         }
1046 
1047         // Now that this is done, check the ready queue for more work.
1048         processReadyQueue(track);
1049     }
1050 
isTransitionKnown(IBinder token)1051     private boolean isTransitionKnown(IBinder token) {
1052         for (int i = 0; i < mPendingTransitions.size(); ++i) {
1053             if (mPendingTransitions.get(i).mToken == token) return true;
1054         }
1055         for (int i = 0; i < mReadyDuringSync.size(); ++i) {
1056             if (mReadyDuringSync.get(i).mToken == token) return true;
1057         }
1058         for (int t = 0; t < mTracks.size(); ++t) {
1059             final Track tr = mTracks.get(t);
1060             for (int i = 0; i < tr.mReadyTransitions.size(); ++i) {
1061                 if (tr.mReadyTransitions.get(i).mToken == token) return true;
1062             }
1063             final ActiveTransition active = tr.mActiveTransition;
1064             if (active == null) continue;
1065             if (active.mToken == token) return true;
1066             if (active.mMerged == null) continue;
1067             for (int m = 0; m < active.mMerged.size(); ++m) {
1068                 if (active.mMerged.get(m).mToken == token) return true;
1069             }
1070         }
1071         return false;
1072     }
1073 
requestStartTransition(@onNull IBinder transitionToken, @Nullable TransitionRequestInfo request)1074     void requestStartTransition(@NonNull IBinder transitionToken,
1075             @Nullable TransitionRequestInfo request) {
1076         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition requested: %s %s",
1077                 transitionToken, request);
1078         if (isTransitionKnown(transitionToken)) {
1079             throw new RuntimeException("Transition already started " + transitionToken);
1080         }
1081         final ActiveTransition active = new ActiveTransition();
1082         WindowContainerTransaction wct = null;
1083 
1084         // If we have sleep, we use a special handler and we try to finish everything ASAP.
1085         if (request.getType() == TRANSIT_SLEEP) {
1086             mSleepHandler.handleRequest(transitionToken, request);
1087             active.mHandler = mSleepHandler;
1088         } else {
1089             for (int i = mHandlers.size() - 1; i >= 0; --i) {
1090                 wct = mHandlers.get(i).handleRequest(transitionToken, request);
1091                 if (wct != null) {
1092                     active.mHandler = mHandlers.get(i);
1093                     break;
1094                 }
1095             }
1096             if (request.getDisplayChange() != null) {
1097                 TransitionRequestInfo.DisplayChange change = request.getDisplayChange();
1098                 if (change.getEndRotation() != change.getStartRotation()) {
1099                     // Is a rotation, so dispatch to all displayChange listeners
1100                     if (wct == null) {
1101                         wct = new WindowContainerTransaction();
1102                     }
1103                     mDisplayController.onDisplayRotateRequested(wct, change.getDisplayId(),
1104                             change.getStartRotation(), change.getEndRotation());
1105                 }
1106             }
1107         }
1108         final boolean isOccludingKeyguard = request.getType() == TRANSIT_KEYGUARD_OCCLUDE
1109                 || ((request.getFlags() & TRANSIT_FLAG_KEYGUARD_OCCLUDING) != 0);
1110         if (isOccludingKeyguard && request.getTriggerTask() != null
1111                 && request.getTriggerTask().getWindowingMode() == WINDOWING_MODE_FREEFORM) {
1112             // This freeform task is on top of keyguard, so its windowing mode should be changed to
1113             // fullscreen.
1114             if (wct == null) {
1115                 wct = new WindowContainerTransaction();
1116             }
1117             wct.setWindowingMode(request.getTriggerTask().token, WINDOWING_MODE_FULLSCREEN);
1118             wct.setBounds(request.getTriggerTask().token, null);
1119         }
1120         mOrganizer.startTransition(transitionToken, wct != null && wct.isEmpty() ? null : wct);
1121         active.mToken = transitionToken;
1122         // Currently, WMCore only does one transition at a time. If it makes a requestStart, it
1123         // is already collecting that transition on core-side, so it will be the next one to
1124         // become ready. There may already be pending transitions added as part of direct
1125         // `startNewTransition` but if we have a request now, it means WM created the request
1126         // transition before it acknowledged any of the pending `startNew` transitions. So, insert
1127         // it at the front.
1128         mPendingTransitions.add(0, active);
1129     }
1130 
1131     /** Start a new transition directly. */
startTransition(@indowManager.TransitionType int type, @NonNull WindowContainerTransaction wct, @Nullable TransitionHandler handler)1132     public IBinder startTransition(@WindowManager.TransitionType int type,
1133             @NonNull WindowContainerTransaction wct, @Nullable TransitionHandler handler) {
1134         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Directly starting a new transition "
1135                 + "type=%d wct=%s handler=%s", type, wct, handler);
1136         final ActiveTransition active = new ActiveTransition();
1137         active.mHandler = handler;
1138         active.mToken = mOrganizer.startNewTransition(type, wct);
1139         mPendingTransitions.add(active);
1140         return active.mToken;
1141     }
1142 
1143     /**
1144      * Finish running animations (almost) immediately when a SLEEP transition comes in. We use this
1145      * as both a way to reduce unnecessary work (animations not visible while screen off) and as a
1146      * failsafe to unblock "stuck" animations (in particular remote animations).
1147      *
1148      * This works by "merging" the sleep transition into the currently-playing transition (even if
1149      * its out-of-order) -- turning SLEEP into a signal. If the playing transition doesn't finish
1150      * within `SYNC_ALLOWANCE_MS` from this merge attempt, this will then finish it directly (and
1151      * send an abort/consumed message).
1152      *
1153      * This is then repeated until there are no more pending sleep transitions.
1154      *
1155      * @param reason The SLEEP transition that triggered this round of finishes. We will continue
1156      *               looping round finishing transitions as long as this is still waiting.
1157      * @param forceFinish When non-null, this is the transition that we last sent the SLEEP merge
1158      *                    signal to -- so it will be force-finished if it's still running.
1159      */
finishForSync(ActiveTransition reason, int trackIdx, @Nullable ActiveTransition forceFinish)1160     private void finishForSync(ActiveTransition reason,
1161             int trackIdx, @Nullable ActiveTransition forceFinish) {
1162         if (!isTransitionKnown(reason.mToken)) {
1163             Log.d(TAG, "finishForSleep: already played sync transition " + reason);
1164             return;
1165         }
1166         final Track track = mTracks.get(trackIdx);
1167         if (forceFinish != null) {
1168             final Track trk = mTracks.get(forceFinish.getTrack());
1169             if (trk != track) {
1170                 Log.e(TAG, "finishForSleep: mismatched Tracks between forceFinish and logic "
1171                         + forceFinish.getTrack() + " vs " + trackIdx);
1172             }
1173             if (trk.mActiveTransition == forceFinish) {
1174                 Log.e(TAG, "Forcing transition to finish due to sync timeout: " + forceFinish);
1175                 forceFinish.mAborted = true;
1176                 // Last notify of it being consumed. Note: mHandler should never be null,
1177                 // but check just to be safe.
1178                 if (forceFinish.mHandler != null) {
1179                     forceFinish.mHandler.onTransitionConsumed(
1180                             forceFinish.mToken, true /* aborted */, null /* finishTransaction */);
1181                 }
1182                 onFinish(forceFinish, null);
1183             }
1184         }
1185         if (track.isIdle() || mReadyDuringSync.isEmpty()) {
1186             // Done finishing things.
1187             return;
1188         }
1189         final SurfaceControl.Transaction dummyT = new SurfaceControl.Transaction();
1190         final TransitionInfo dummyInfo = new TransitionInfo(TRANSIT_SLEEP, 0 /* flags */);
1191         while (track.mActiveTransition != null && !mReadyDuringSync.isEmpty()) {
1192             final ActiveTransition playing = track.mActiveTransition;
1193             final ActiveTransition nextSync = mReadyDuringSync.get(0);
1194             if (!nextSync.isSync()) {
1195                 Log.e(TAG, "Somehow blocked on a non-sync transition? " + nextSync);
1196             }
1197             // Attempt to merge a SLEEP info to signal that the playing transition needs to
1198             // fast-forward.
1199             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Attempt to merge sync %s"
1200                     + " into %s via a SLEEP proxy", nextSync, playing);
1201             playing.mHandler.mergeAnimation(nextSync.mToken, dummyInfo, dummyT,
1202                     playing.mToken, (wct) -> {});
1203             // it's possible to complete immediately. If that happens, just repeat the signal
1204             // loop until we either finish everything or start playing an animation that isn't
1205             // finishing immediately.
1206             if (track.mActiveTransition == playing) {
1207                 if (!mDisableForceSync) {
1208                     // Give it a short amount of time to process it before forcing.
1209                     final int tolerance = KeyguardTransitionHandler.handles(playing.mInfo)
1210                             ? SYNC_ALLOWANCE_KEYGUARD_MS
1211                             : SYNC_ALLOWANCE_MS;
1212                     mMainExecutor.executeDelayed(
1213                             () -> finishForSync(reason, trackIdx, playing), tolerance);
1214                 }
1215                 break;
1216             }
1217         }
1218     }
1219 
1220     /**
1221      * Interface for a callback that must be called after a TransitionHandler finishes playing an
1222      * animation.
1223      */
1224     public interface TransitionFinishCallback {
1225         /**
1226          * This must be called on the main thread when a transition finishes playing an animation.
1227          * The transition must not touch the surfaces after this has been called.
1228          *
1229          * @param wct A WindowContainerTransaction to run along with the transition clean-up.
1230          */
onTransitionFinished(@ullable WindowContainerTransaction wct)1231         void onTransitionFinished(@Nullable WindowContainerTransaction wct);
1232     }
1233 
1234     /**
1235      * Interface for something which can handle a subset of transitions.
1236      */
1237     public interface TransitionHandler {
1238         /**
1239          * Starts a transition animation. This is always called if handleRequest returned non-null
1240          * for a particular transition. Otherwise, it is only called if no other handler before
1241          * it handled the transition.
1242          * @param startTransaction the transaction given to the handler to be applied before the
1243          *                         transition animation. Note the handler is expected to call on
1244          *                         {@link SurfaceControl.Transaction#apply()} for startTransaction.
1245          * @param finishTransaction the transaction given to the handler to be applied after the
1246          *                       transition animation. Unlike startTransaction, the handler is NOT
1247          *                       expected to apply this transaction. The Transition system will
1248          *                       apply it when finishCallback is called.
1249          * @param finishCallback Call this when finished. This MUST be called on main thread.
1250          * @return true if transition was handled, false if not (falls-back to default).
1251          */
startAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull TransitionFinishCallback finishCallback)1252         boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
1253                 @NonNull SurfaceControl.Transaction startTransaction,
1254                 @NonNull SurfaceControl.Transaction finishTransaction,
1255                 @NonNull TransitionFinishCallback finishCallback);
1256 
1257         /**
1258          * Attempts to merge a different transition's animation into an animation that this handler
1259          * is currently playing. If a merge is not possible/supported, this should be a no-op.
1260          *
1261          * This gets called if another transition becomes ready while this handler is still playing
1262          * an animation. This is called regardless of whether this handler claims to support that
1263          * particular transition or not.
1264          *
1265          * When this happens, there are 2 options:
1266          *  1. Do nothing. This effectively rejects the merge request. This is the "safest" option.
1267          *  2. Merge the incoming transition into this one. The implementation is up to this
1268          *     handler. To indicate that this handler has "consumed" the merge transition, it
1269          *     must call the finishCallback immediately, or at-least before the original
1270          *     transition's finishCallback is called.
1271          *
1272          * @param transition This is the transition that wants to be merged.
1273          * @param info Information about what is changing in the transition.
1274          * @param t Contains surface changes that resulted from the transition.
1275          * @param mergeTarget This is the transition that we are attempting to merge with (ie. the
1276          *                    one this handler is currently already animating).
1277          * @param finishCallback Call this if merged. This MUST be called on main thread.
1278          */
mergeAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, @NonNull TransitionFinishCallback finishCallback)1279         default void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
1280                 @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
1281                 @NonNull TransitionFinishCallback finishCallback) { }
1282 
1283         /**
1284          * Potentially handles a startTransition request.
1285          *
1286          * @param transition The transition whose start is being requested.
1287          * @param request Information about what is requested.
1288          * @return WCT to apply with transition-start or null. If a WCT is returned here, this
1289          *         handler will be the first in line to animate.
1290          */
1291         @Nullable
handleRequest(@onNull IBinder transition, @NonNull TransitionRequestInfo request)1292         WindowContainerTransaction handleRequest(@NonNull IBinder transition,
1293                 @NonNull TransitionRequestInfo request);
1294 
1295         /**
1296          * Called when a transition which was already "claimed" by this handler has been merged
1297          * into another animation or has been aborted. Gives this handler a chance to clean-up any
1298          * expectations.
1299          *
1300          * @param transition The transition been consumed.
1301          * @param aborted Whether the transition is aborted or not.
1302          * @param finishTransaction The transaction to be applied after the transition animated.
1303          */
onTransitionConsumed(@onNull IBinder transition, boolean aborted, @Nullable SurfaceControl.Transaction finishTransaction)1304         default void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
1305                 @Nullable SurfaceControl.Transaction finishTransaction) { }
1306 
1307         /**
1308          * Sets transition animation scale settings value to handler.
1309          *
1310          * @param scale The setting value of transition animation scale.
1311          */
setAnimScaleSetting(float scale)1312         default void setAnimScaleSetting(float scale) {}
1313     }
1314 
1315     /**
1316      * Interface for something that needs to know the lifecycle of some transitions, but never
1317      * handles any transition by itself.
1318      */
1319     public interface TransitionObserver {
1320         /**
1321          * Called when the transition is ready to play. It may later be merged into other
1322          * transitions. Note this doesn't mean this transition will be played anytime soon.
1323          *
1324          * @param transition the unique token of this transition
1325          * @param startTransaction the transaction given to the handler to be applied before the
1326          *                         transition animation. This will be applied when the transition
1327          *                         handler that handles this transition starts the transition.
1328          * @param finishTransaction the transaction given to the handler to be applied after the
1329          *                          transition animation. The Transition system will apply it when
1330          *                          finishCallback is called by the transition handler.
1331          */
onTransitionReady(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction)1332         void onTransitionReady(@NonNull IBinder transition, @NonNull TransitionInfo info,
1333                 @NonNull SurfaceControl.Transaction startTransaction,
1334                 @NonNull SurfaceControl.Transaction finishTransaction);
1335 
1336         /**
1337          * Called when the transition is starting to play. It isn't called for merged transitions.
1338          *
1339          * @param transition the unique token of this transition
1340          */
onTransitionStarting(@onNull IBinder transition)1341         void onTransitionStarting(@NonNull IBinder transition);
1342 
1343         /**
1344          * Called when a transition is merged into another transition. There won't be any following
1345          * lifecycle calls for the merged transition.
1346          *
1347          * @param merged the unique token of the transition that's merged to another one
1348          * @param playing the unique token of the transition that accepts the merge
1349          */
onTransitionMerged(@onNull IBinder merged, @NonNull IBinder playing)1350         void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing);
1351 
1352         /**
1353          * Called when the transition is finished. This isn't called for merged transitions.
1354          *
1355          * @param transition the unique token of this transition
1356          * @param aborted {@code true} if this transition is aborted; {@code false} otherwise.
1357          */
onTransitionFinished(@onNull IBinder transition, boolean aborted)1358         void onTransitionFinished(@NonNull IBinder transition, boolean aborted);
1359     }
1360 
1361     @BinderThread
1362     private class TransitionPlayerImpl extends ITransitionPlayer.Stub {
1363         @Override
onTransitionReady(IBinder iBinder, TransitionInfo transitionInfo, SurfaceControl.Transaction t, SurfaceControl.Transaction finishT)1364         public void onTransitionReady(IBinder iBinder, TransitionInfo transitionInfo,
1365                 SurfaceControl.Transaction t, SurfaceControl.Transaction finishT)
1366                 throws RemoteException {
1367             mMainExecutor.execute(() -> Transitions.this.onTransitionReady(
1368                     iBinder, transitionInfo, t, finishT));
1369         }
1370 
1371         @Override
requestStartTransition(IBinder iBinder, TransitionRequestInfo request)1372         public void requestStartTransition(IBinder iBinder,
1373                 TransitionRequestInfo request) throws RemoteException {
1374             mMainExecutor.execute(() -> Transitions.this.requestStartTransition(iBinder, request));
1375         }
1376     }
1377 
1378     /**
1379      * The interface for calls from outside the Shell, within the host process.
1380      */
1381     @ExternalThread
1382     private class ShellTransitionImpl implements ShellTransitions {
1383         @Override
registerRemote(@onNull TransitionFilter filter, @NonNull RemoteTransition remoteTransition)1384         public void registerRemote(@NonNull TransitionFilter filter,
1385                 @NonNull RemoteTransition remoteTransition) {
1386             mMainExecutor.execute(() -> {
1387                 mRemoteTransitionHandler.addFiltered(filter, remoteTransition);
1388             });
1389         }
1390 
1391         @Override
unregisterRemote(@onNull RemoteTransition remoteTransition)1392         public void unregisterRemote(@NonNull RemoteTransition remoteTransition) {
1393             mMainExecutor.execute(() -> {
1394                 mRemoteTransitionHandler.removeFiltered(remoteTransition);
1395             });
1396         }
1397     }
1398 
1399     /**
1400      * The interface for calls from outside the host process.
1401      */
1402     @BinderThread
1403     private static class IShellTransitionsImpl extends IShellTransitions.Stub
1404             implements ExternalInterfaceBinder {
1405         private Transitions mTransitions;
1406 
IShellTransitionsImpl(Transitions transitions)1407         IShellTransitionsImpl(Transitions transitions) {
1408             mTransitions = transitions;
1409         }
1410 
1411         /**
1412          * Invalidates this instance, preventing future calls from updating the controller.
1413          */
1414         @Override
invalidate()1415         public void invalidate() {
1416             mTransitions = null;
1417         }
1418 
1419         @Override
registerRemote(@onNull TransitionFilter filter, @NonNull RemoteTransition remoteTransition)1420         public void registerRemote(@NonNull TransitionFilter filter,
1421                 @NonNull RemoteTransition remoteTransition) {
1422             executeRemoteCallWithTaskPermission(mTransitions, "registerRemote",
1423                     (transitions) -> {
1424                         transitions.mRemoteTransitionHandler.addFiltered(filter, remoteTransition);
1425                     });
1426         }
1427 
1428         @Override
unregisterRemote(@onNull RemoteTransition remoteTransition)1429         public void unregisterRemote(@NonNull RemoteTransition remoteTransition) {
1430             executeRemoteCallWithTaskPermission(mTransitions, "unregisterRemote",
1431                     (transitions) -> {
1432                         transitions.mRemoteTransitionHandler.removeFiltered(remoteTransition);
1433                     });
1434         }
1435 
1436         @Override
getShellApplyToken()1437         public IBinder getShellApplyToken() {
1438             return SurfaceControl.Transaction.getDefaultApplyToken();
1439         }
1440     }
1441 
1442     private class SettingsObserver extends ContentObserver {
1443 
SettingsObserver()1444         SettingsObserver() {
1445             super(null);
1446         }
1447 
1448         @Override
onChange(boolean selfChange)1449         public void onChange(boolean selfChange) {
1450             super.onChange(selfChange);
1451             mTransitionAnimationScaleSetting = getTransitionAnimationScaleSetting();
1452 
1453             mMainExecutor.execute(() -> dispatchAnimScaleSetting(mTransitionAnimationScaleSetting));
1454         }
1455     }
1456 
1457 
1458     @Override
onShellCommand(String[] args, PrintWriter pw)1459     public boolean onShellCommand(String[] args, PrintWriter pw) {
1460         switch (args[0]) {
1461             case "tracing": {
1462                 mTracer.onShellCommand(Arrays.copyOfRange(args, 1, args.length), pw);
1463                 return true;
1464             }
1465             default: {
1466                 pw.println("Invalid command: " + args[0]);
1467                 printShellCommandHelp(pw, "");
1468                 return false;
1469             }
1470         }
1471     }
1472 
1473     @Override
printShellCommandHelp(PrintWriter pw, String prefix)1474     public void printShellCommandHelp(PrintWriter pw, String prefix) {
1475         pw.println(prefix + "tracing");
1476         mTracer.printShellCommandHelp(pw, prefix + "  ");
1477     }
1478 }
1479