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.view.WindowManager.TRANSIT_CHANGE;
20 import static android.view.WindowManager.TRANSIT_CLOSE;
21 import static android.view.WindowManager.TRANSIT_FIRST_CUSTOM;
22 import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
23 import static android.view.WindowManager.TRANSIT_OPEN;
24 import static android.view.WindowManager.TRANSIT_TO_BACK;
25 import static android.view.WindowManager.TRANSIT_TO_FRONT;
26 import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
27 import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
28 
29 import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
30 
31 import android.annotation.NonNull;
32 import android.annotation.Nullable;
33 import android.content.ContentResolver;
34 import android.content.Context;
35 import android.database.ContentObserver;
36 import android.os.IBinder;
37 import android.os.RemoteException;
38 import android.os.SystemProperties;
39 import android.provider.Settings;
40 import android.util.Log;
41 import android.view.SurfaceControl;
42 import android.view.WindowManager;
43 import android.window.ITransitionPlayer;
44 import android.window.RemoteTransition;
45 import android.window.TransitionFilter;
46 import android.window.TransitionInfo;
47 import android.window.TransitionMetrics;
48 import android.window.TransitionRequestInfo;
49 import android.window.WindowContainerTransaction;
50 import android.window.WindowContainerTransactionCallback;
51 import android.window.WindowOrganizer;
52 
53 import androidx.annotation.BinderThread;
54 
55 import com.android.internal.R;
56 import com.android.internal.annotations.VisibleForTesting;
57 import com.android.internal.protolog.common.ProtoLog;
58 import com.android.wm.shell.ShellTaskOrganizer;
59 import com.android.wm.shell.common.DisplayController;
60 import com.android.wm.shell.common.RemoteCallable;
61 import com.android.wm.shell.common.ShellExecutor;
62 import com.android.wm.shell.common.TransactionPool;
63 import com.android.wm.shell.common.annotations.ExternalThread;
64 import com.android.wm.shell.protolog.ShellProtoLogGroup;
65 
66 import java.util.ArrayList;
67 import java.util.Arrays;
68 
69 /** Plays transition animations */
70 public class Transitions implements RemoteCallable<Transitions> {
71     static final String TAG = "ShellTransitions";
72 
73     /** Set to {@code true} to enable shell transitions. */
74     public static final boolean ENABLE_SHELL_TRANSITIONS =
75             SystemProperties.getBoolean("persist.debug.shell_transit", false);
76 
77     /** Transition type for dismissing split-screen via dragging the divider off the screen. */
78     public static final int TRANSIT_SPLIT_DISMISS_SNAP = TRANSIT_FIRST_CUSTOM + 1;
79 
80     /** Transition type for launching 2 tasks simultaneously. */
81     public static final int TRANSIT_SPLIT_SCREEN_PAIR_OPEN = TRANSIT_FIRST_CUSTOM + 2;
82 
83     /** Transition type for exiting PIP via the Shell, via pressing the expand button. */
84     public static final int TRANSIT_EXIT_PIP = TRANSIT_FIRST_CUSTOM + 3;
85 
86     /** Transition type for removing PIP via the Shell, either via Dismiss bubble or Close. */
87     public static final int TRANSIT_REMOVE_PIP = TRANSIT_FIRST_CUSTOM + 4;
88 
89     /** Transition type for entering split by opening an app into side-stage. */
90     public static final int TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE = TRANSIT_FIRST_CUSTOM + 5;
91 
92     private final WindowOrganizer mOrganizer;
93     private final Context mContext;
94     private final ShellExecutor mMainExecutor;
95     private final ShellExecutor mAnimExecutor;
96     private final TransitionPlayerImpl mPlayerImpl;
97     private final RemoteTransitionHandler mRemoteTransitionHandler;
98     private final ShellTransitionImpl mImpl = new ShellTransitionImpl();
99 
100     /** List of possible handlers. Ordered by specificity (eg. tapped back to front). */
101     private final ArrayList<TransitionHandler> mHandlers = new ArrayList<>();
102 
103     private float mTransitionAnimationScaleSetting = 1.0f;
104 
105     private static final class ActiveTransition {
106         IBinder mToken;
107         TransitionHandler mHandler;
108         boolean mMerged;
109         boolean mAborted;
110         TransitionInfo mInfo;
111         SurfaceControl.Transaction mStartT;
112         SurfaceControl.Transaction mFinishT;
113     }
114 
115     /** Keeps track of currently playing transitions in the order of receipt. */
116     private final ArrayList<ActiveTransition> mActiveTransitions = new ArrayList<>();
117 
Transitions(@onNull WindowOrganizer organizer, @NonNull TransactionPool pool, @NonNull DisplayController displayController, @NonNull Context context, @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor)118     public Transitions(@NonNull WindowOrganizer organizer, @NonNull TransactionPool pool,
119             @NonNull DisplayController displayController, @NonNull Context context,
120             @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) {
121         mOrganizer = organizer;
122         mContext = context;
123         mMainExecutor = mainExecutor;
124         mAnimExecutor = animExecutor;
125         mPlayerImpl = new TransitionPlayerImpl();
126         // The very last handler (0 in the list) should be the default one.
127         mHandlers.add(new DefaultTransitionHandler(displayController, pool, context, mainExecutor,
128                 animExecutor));
129         // Next lowest priority is remote transitions.
130         mRemoteTransitionHandler = new RemoteTransitionHandler(mainExecutor);
131         mHandlers.add(mRemoteTransitionHandler);
132 
133         ContentResolver resolver = context.getContentResolver();
134         mTransitionAnimationScaleSetting = Settings.Global.getFloat(resolver,
135                 Settings.Global.TRANSITION_ANIMATION_SCALE,
136                 context.getResources().getFloat(
137                         R.dimen.config_appTransitionAnimationDurationScaleDefault));
138         dispatchAnimScaleSetting(mTransitionAnimationScaleSetting);
139 
140         resolver.registerContentObserver(
141                 Settings.Global.getUriFor(Settings.Global.TRANSITION_ANIMATION_SCALE), false,
142                 new SettingsObserver());
143     }
144 
Transitions()145     private Transitions() {
146         mOrganizer = null;
147         mContext = null;
148         mMainExecutor = null;
149         mAnimExecutor = null;
150         mPlayerImpl = null;
151         mRemoteTransitionHandler = null;
152     }
153 
asRemoteTransitions()154     public ShellTransitions asRemoteTransitions() {
155         return mImpl;
156     }
157 
158     @Override
getContext()159     public Context getContext() {
160         return mContext;
161     }
162 
163     @Override
getRemoteCallExecutor()164     public ShellExecutor getRemoteCallExecutor() {
165         return mMainExecutor;
166     }
167 
dispatchAnimScaleSetting(float scale)168     private void dispatchAnimScaleSetting(float scale) {
169         for (int i = mHandlers.size() - 1; i >= 0; --i) {
170             mHandlers.get(i).setAnimScaleSetting(scale);
171         }
172     }
173 
174     /** Register this transition handler with Core */
register(ShellTaskOrganizer taskOrganizer)175     public void register(ShellTaskOrganizer taskOrganizer) {
176         if (mPlayerImpl == null) return;
177         taskOrganizer.registerTransitionPlayer(mPlayerImpl);
178         // Pre-load the instance.
179         TransitionMetrics.getInstance();
180     }
181 
182     /**
183      * Adds a handler candidate.
184      * @see TransitionHandler
185      */
addHandler(@onNull TransitionHandler handler)186     public void addHandler(@NonNull TransitionHandler handler) {
187         mHandlers.add(handler);
188     }
189 
getMainExecutor()190     public ShellExecutor getMainExecutor() {
191         return mMainExecutor;
192     }
193 
getAnimExecutor()194     public ShellExecutor getAnimExecutor() {
195         return mAnimExecutor;
196     }
197 
198     /** Only use this in tests. This is used to avoid running animations during tests. */
199     @VisibleForTesting
replaceDefaultHandlerForTest(TransitionHandler handler)200     void replaceDefaultHandlerForTest(TransitionHandler handler) {
201         mHandlers.set(0, handler);
202     }
203 
204     /** Register a remote transition to be used when `filter` matches an incoming transition */
registerRemote(@onNull TransitionFilter filter, @NonNull RemoteTransition remoteTransition)205     public void registerRemote(@NonNull TransitionFilter filter,
206             @NonNull RemoteTransition remoteTransition) {
207         mRemoteTransitionHandler.addFiltered(filter, remoteTransition);
208     }
209 
210     /** Unregisters a remote transition and all associated filters */
unregisterRemote(@onNull RemoteTransition remoteTransition)211     public void unregisterRemote(@NonNull RemoteTransition remoteTransition) {
212         mRemoteTransitionHandler.removeFiltered(remoteTransition);
213     }
214 
215     /** @return true if the transition was triggered by opening something vs closing something */
isOpeningType(@indowManager.TransitionType int type)216     public static boolean isOpeningType(@WindowManager.TransitionType int type) {
217         return type == TRANSIT_OPEN
218                 || type == TRANSIT_TO_FRONT
219                 || type == TRANSIT_KEYGUARD_GOING_AWAY;
220     }
221 
222     /** @return true if the transition was triggered by closing something vs opening something */
isClosingType(@indowManager.TransitionType int type)223     public static boolean isClosingType(@WindowManager.TransitionType int type) {
224         return type == TRANSIT_CLOSE || type == TRANSIT_TO_BACK;
225     }
226 
227     /**
228      * Sets up visibility/alpha/transforms to resemble the starting state of an animation.
229      */
setupStartState(@onNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT)230     private static void setupStartState(@NonNull TransitionInfo info,
231             @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) {
232         boolean isOpening = isOpeningType(info.getType());
233         for (int i = info.getChanges().size() - 1; i >= 0; --i) {
234             final TransitionInfo.Change change = info.getChanges().get(i);
235             final SurfaceControl leash = change.getLeash();
236             final int mode = info.getChanges().get(i).getMode();
237 
238             // Don't move anything that isn't independent within its parents
239             if (!TransitionInfo.isIndependent(change, info)) {
240                 if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT || mode == TRANSIT_CHANGE) {
241                     t.show(leash);
242                     t.setMatrix(leash, 1, 0, 0, 1);
243                     t.setAlpha(leash, 1.f);
244                     t.setPosition(leash, change.getEndRelOffset().x, change.getEndRelOffset().y);
245                 }
246                 continue;
247             }
248 
249             if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
250                 t.show(leash);
251                 t.setMatrix(leash, 1, 0, 0, 1);
252                 if (isOpening
253                         // If this is a transferred starting window, we want it immediately visible.
254                         && (change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) == 0) {
255                     t.setAlpha(leash, 0.f);
256                     // fix alpha in finish transaction in case the animator itself no-ops.
257                     finishT.setAlpha(leash, 1.f);
258                 }
259             } else if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) {
260                 // Wallpaper is a bit of an anomaly: it's visibility is tied to other WindowStates.
261                 // As a result, we actually can't hide it's WindowToken because there may not be a
262                 // transition associated with it becoming visible again. Fortunately, since it is
263                 // always z-ordered to the back, we don't have to worry about it flickering to the
264                 // front during reparenting, so the hide here isn't necessary for it.
265                 if ((change.getFlags() & FLAG_IS_WALLPAPER) == 0) {
266                     finishT.hide(leash);
267                 }
268             }
269         }
270     }
271 
272     /**
273      * Reparents all participants into a shared parent and orders them based on: the global transit
274      * type, their transit mode, and their destination z-order.
275      */
setupAnimHierarchy(@onNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT)276     private static void setupAnimHierarchy(@NonNull TransitionInfo info,
277             @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) {
278         boolean isOpening = isOpeningType(info.getType());
279         if (info.getRootLeash().isValid()) {
280             t.show(info.getRootLeash());
281         }
282         // Put animating stuff above this line and put static stuff below it.
283         int zSplitLine = info.getChanges().size();
284         // changes should be ordered top-to-bottom in z
285         for (int i = info.getChanges().size() - 1; i >= 0; --i) {
286             final TransitionInfo.Change change = info.getChanges().get(i);
287             final SurfaceControl leash = change.getLeash();
288             final int mode = info.getChanges().get(i).getMode();
289 
290             // Don't reparent anything that isn't independent within its parents
291             if (!TransitionInfo.isIndependent(change, info)) {
292                 continue;
293             }
294 
295             boolean hasParent = change.getParent() != null;
296 
297             if (!hasParent) {
298                 t.reparent(leash, info.getRootLeash());
299                 t.setPosition(leash, change.getStartAbsBounds().left - info.getRootOffset().x,
300                         change.getStartAbsBounds().top - info.getRootOffset().y);
301             }
302             // Put all the OPEN/SHOW on top
303             if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
304                 if (isOpening) {
305                     // put on top
306                     t.setLayer(leash, zSplitLine + info.getChanges().size() - i);
307                 } else {
308                     // put on bottom
309                     t.setLayer(leash, zSplitLine - i);
310                 }
311             } else if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) {
312                 if (isOpening) {
313                     // put on bottom and leave visible
314                     t.setLayer(leash, zSplitLine - i);
315                 } else {
316                     // put on top
317                     t.setLayer(leash, zSplitLine + info.getChanges().size() - i);
318                 }
319             } else { // CHANGE or other
320                 t.setLayer(leash, zSplitLine + info.getChanges().size() - i);
321             }
322         }
323     }
324 
findActiveTransition(IBinder token)325     private int findActiveTransition(IBinder token) {
326         for (int i = mActiveTransitions.size() - 1; i >= 0; --i) {
327             if (mActiveTransitions.get(i).mToken == token) return i;
328         }
329         return -1;
330     }
331 
332     @VisibleForTesting
onTransitionReady(@onNull IBinder transitionToken, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT)333     void onTransitionReady(@NonNull IBinder transitionToken, @NonNull TransitionInfo info,
334             @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) {
335         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady %s: %s",
336                 transitionToken, info);
337         final int activeIdx = findActiveTransition(transitionToken);
338         if (activeIdx < 0) {
339             throw new IllegalStateException("Got transitionReady for non-active transition "
340                     + transitionToken + ". expecting one of "
341                     + Arrays.toString(mActiveTransitions.stream().map(
342                             activeTransition -> activeTransition.mToken).toArray()));
343         }
344         if (!info.getRootLeash().isValid()) {
345             // Invalid root-leash implies that the transition is empty/no-op, so just do
346             // housekeeping and return.
347             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Invalid root leash (%s): %s",
348                     transitionToken, info);
349             t.apply();
350             onAbort(transitionToken);
351             return;
352         }
353 
354         final ActiveTransition active = mActiveTransitions.get(activeIdx);
355         active.mInfo = info;
356         active.mStartT = t;
357         active.mFinishT = finishT;
358         setupStartState(active.mInfo, active.mStartT, active.mFinishT);
359 
360         if (activeIdx > 0) {
361             // This is now playing at the same time as an existing animation, so try merging it.
362             attemptMergeTransition(mActiveTransitions.get(0), active);
363             return;
364         }
365         // The normal case, just play it.
366         playTransition(active);
367     }
368 
369     /**
370      * Attempt to merge by delegating the transition start to the handler of the currently
371      * playing transition.
372      */
attemptMergeTransition(@onNull ActiveTransition playing, @NonNull ActiveTransition merging)373     void attemptMergeTransition(@NonNull ActiveTransition playing,
374             @NonNull ActiveTransition merging) {
375         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition %s ready while"
376                 + " another transition %s is still animating. Notify the animating transition"
377                 + " in case they can be merged", merging.mToken, playing.mToken);
378         playing.mHandler.mergeAnimation(merging.mToken, merging.mInfo, merging.mStartT,
379                 playing.mToken, (wct, cb) -> onFinish(merging.mToken, wct, cb));
380     }
381 
startAnimation(@onNull ActiveTransition active, TransitionHandler handler)382     boolean startAnimation(@NonNull ActiveTransition active, TransitionHandler handler) {
383         return handler.startAnimation(active.mToken, active.mInfo, active.mStartT, active.mFinishT,
384                 (wct, cb) -> onFinish(active.mToken, wct, cb));
385     }
386 
playTransition(@onNull ActiveTransition active)387     void playTransition(@NonNull ActiveTransition active) {
388         setupAnimHierarchy(active.mInfo, active.mStartT, active.mFinishT);
389 
390         // If a handler already chose to run this animation, try delegating to it first.
391         if (active.mHandler != null) {
392             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " try firstHandler %s",
393                     active.mHandler);
394             if (startAnimation(active, active.mHandler)) {
395                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by firstHandler");
396                 return;
397             }
398         }
399         // Otherwise give every other handler a chance (in order)
400         for (int i = mHandlers.size() - 1; i >= 0; --i) {
401             if (mHandlers.get(i) == active.mHandler) continue;
402             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " try handler %s",
403                     mHandlers.get(i));
404             if (startAnimation(active, mHandlers.get(i))) {
405                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by %s",
406                         mHandlers.get(i));
407                 active.mHandler = mHandlers.get(i);
408                 return;
409             }
410         }
411         throw new IllegalStateException(
412                 "This shouldn't happen, maybe the default handler is broken.");
413     }
414 
415     /** Special version of finish just for dealing with no-op/invalid transitions. */
onAbort(IBinder transition)416     private void onAbort(IBinder transition) {
417         onFinish(transition, null /* wct */, null /* wctCB */, true /* abort */);
418     }
419 
onFinish(IBinder transition, @Nullable WindowContainerTransaction wct, @Nullable WindowContainerTransactionCallback wctCB)420     private void onFinish(IBinder transition,
421             @Nullable WindowContainerTransaction wct,
422             @Nullable WindowContainerTransactionCallback wctCB) {
423         onFinish(transition, wct, wctCB, false /* abort */);
424     }
425 
onFinish(IBinder transition, @Nullable WindowContainerTransaction wct, @Nullable WindowContainerTransactionCallback wctCB, boolean abort)426     private void onFinish(IBinder transition,
427             @Nullable WindowContainerTransaction wct,
428             @Nullable WindowContainerTransactionCallback wctCB,
429             boolean abort) {
430         int activeIdx = findActiveTransition(transition);
431         if (activeIdx < 0) {
432             Log.e(TAG, "Trying to finish a non-running transition. Either remote crashed or "
433                     + " a handler didn't properly deal with a merge.", new RuntimeException());
434             return;
435         } else if (activeIdx > 0) {
436             // This transition was merged.
437             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition was merged (abort=%b:"
438                     + " %s", abort, transition);
439             final ActiveTransition active = mActiveTransitions.get(activeIdx);
440             active.mMerged = true;
441             active.mAborted = abort;
442             if (active.mHandler != null) {
443                 active.mHandler.onTransitionMerged(active.mToken);
444             }
445             return;
446         }
447         mActiveTransitions.get(activeIdx).mAborted = abort;
448         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
449                 "Transition animation finished (abort=%b), notifying core %s", abort, transition);
450         // Merge all relevant transactions together
451         SurfaceControl.Transaction fullFinish = mActiveTransitions.get(activeIdx).mFinishT;
452         for (int iA = activeIdx + 1; iA < mActiveTransitions.size(); ++iA) {
453             final ActiveTransition toMerge = mActiveTransitions.get(iA);
454             if (!toMerge.mMerged) break;
455             // aborted transitions have no start/finish transactions
456             if (mActiveTransitions.get(iA).mStartT == null) break;
457             if (fullFinish == null) {
458                 fullFinish = new SurfaceControl.Transaction();
459             }
460             // Include start. It will be a no-op if it was already applied. Otherwise, we need it
461             // to maintain consistent state.
462             fullFinish.merge(mActiveTransitions.get(iA).mStartT);
463             fullFinish.merge(mActiveTransitions.get(iA).mFinishT);
464         }
465         if (fullFinish != null) {
466             fullFinish.apply();
467         }
468         // Now perform all the finishes.
469         mActiveTransitions.remove(activeIdx);
470         mOrganizer.finishTransition(transition, wct, wctCB);
471         while (activeIdx < mActiveTransitions.size()) {
472             if (!mActiveTransitions.get(activeIdx).mMerged) break;
473             ActiveTransition merged = mActiveTransitions.remove(activeIdx);
474             mOrganizer.finishTransition(merged.mToken, null /* wct */, null /* wctCB */);
475         }
476         // sift through aborted transitions
477         while (mActiveTransitions.size() > activeIdx
478                 && mActiveTransitions.get(activeIdx).mAborted) {
479             ActiveTransition aborted = mActiveTransitions.remove(activeIdx);
480             mOrganizer.finishTransition(aborted.mToken, null /* wct */, null /* wctCB */);
481         }
482         if (mActiveTransitions.size() <= activeIdx) {
483             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "All active transition animations "
484                     + "finished");
485             return;
486         }
487         // Start animating the next active transition
488         final ActiveTransition next = mActiveTransitions.get(activeIdx);
489         if (next.mInfo == null) {
490             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Pending transition after one"
491                     + " finished, but it isn't ready yet.");
492             return;
493         }
494         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Pending transitions after one"
495                 + " finished, so start the next one.");
496         playTransition(next);
497         // Now try to merge the rest of the transitions (re-acquire activeIdx since next may have
498         // finished immediately)
499         activeIdx = findActiveTransition(next.mToken);
500         if (activeIdx < 0) {
501             // This means 'next' finished immediately and thus re-entered this function. Since
502             // that is the case, just return here since all relevant logic has already run in the
503             // re-entered call.
504             return;
505         }
506 
507         // This logic is also convoluted because 'next' may finish immediately in response to any of
508         // the merge requests (eg. if it decided to "cancel" itself).
509         int mergeIdx = activeIdx + 1;
510         while (mergeIdx < mActiveTransitions.size()) {
511             ActiveTransition mergeCandidate = mActiveTransitions.get(mergeIdx);
512             if (mergeCandidate.mAborted) {
513                 // transition was aborted, so we can skip for now (still leave it in the list
514                 // so that it gets cleaned-up in the right order).
515                 ++mergeIdx;
516                 continue;
517             }
518             if (mergeCandidate.mMerged) {
519                 throw new IllegalStateException("Can't merge a transition after not-merging"
520                         + " a preceding one.");
521             }
522             attemptMergeTransition(next, mergeCandidate);
523             mergeIdx = findActiveTransition(mergeCandidate.mToken);
524             if (mergeIdx < 0) {
525                 // This means 'next' finished immediately and thus re-entered this function. Since
526                 // that is the case, just return here since all relevant logic has already run in
527                 // the re-entered call.
528                 return;
529             }
530             ++mergeIdx;
531         }
532     }
533 
requestStartTransition(@onNull IBinder transitionToken, @Nullable TransitionRequestInfo request)534     void requestStartTransition(@NonNull IBinder transitionToken,
535             @Nullable TransitionRequestInfo request) {
536         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition requested: %s %s",
537                 transitionToken, request);
538         if (findActiveTransition(transitionToken) >= 0) {
539             throw new RuntimeException("Transition already started " + transitionToken);
540         }
541         final ActiveTransition active = new ActiveTransition();
542         WindowContainerTransaction wct = null;
543         for (int i = mHandlers.size() - 1; i >= 0; --i) {
544             wct = mHandlers.get(i).handleRequest(transitionToken, request);
545             if (wct != null) {
546                 active.mHandler = mHandlers.get(i);
547                 break;
548             }
549         }
550         active.mToken = mOrganizer.startTransition(
551                 request.getType(), transitionToken, wct);
552         mActiveTransitions.add(active);
553     }
554 
555     /** Start a new transition directly. */
startTransition(@indowManager.TransitionType int type, @NonNull WindowContainerTransaction wct, @Nullable TransitionHandler handler)556     public IBinder startTransition(@WindowManager.TransitionType int type,
557             @NonNull WindowContainerTransaction wct, @Nullable TransitionHandler handler) {
558         final ActiveTransition active = new ActiveTransition();
559         active.mHandler = handler;
560         active.mToken = mOrganizer.startTransition(type, null /* token */, wct);
561         mActiveTransitions.add(active);
562         return active.mToken;
563     }
564 
565     /**
566      * Interface for a callback that must be called after a TransitionHandler finishes playing an
567      * animation.
568      */
569     public interface TransitionFinishCallback {
570         /**
571          * This must be called on the main thread when a transition finishes playing an animation.
572          * The transition must not touch the surfaces after this has been called.
573          *
574          * @param wct A WindowContainerTransaction to run along with the transition clean-up.
575          * @param wctCB A sync callback that will be run when the transition clean-up is done and
576          *              wct has been applied.
577          */
onTransitionFinished(@ullable WindowContainerTransaction wct, @Nullable WindowContainerTransactionCallback wctCB)578         void onTransitionFinished(@Nullable WindowContainerTransaction wct,
579                 @Nullable WindowContainerTransactionCallback wctCB);
580     }
581 
582     /**
583      * Interface for something which can handle a subset of transitions.
584      */
585     public interface TransitionHandler {
586         /**
587          * Starts a transition animation. This is always called if handleRequest returned non-null
588          * for a particular transition. Otherwise, it is only called if no other handler before
589          * it handled the transition.
590          * @param startTransaction the transaction given to the handler to be applied before the
591          *                         transition animation. Note the handler is expected to call on
592          *                         {@link SurfaceControl.Transaction#apply()} for startTransaction.
593          * @param finishTransaction the transaction given to the handler to be applied after the
594          *                       transition animation. Unlike startTransaction, the handler is NOT
595          *                       expected to apply this transaction. The Transition system will
596          *                       apply it when finishCallback is called.
597          * @param finishCallback Call this when finished. This MUST be called on main thread.
598          * @return true if transition was handled, false if not (falls-back to default).
599          */
startAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull TransitionFinishCallback finishCallback)600         boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
601                 @NonNull SurfaceControl.Transaction startTransaction,
602                 @NonNull SurfaceControl.Transaction finishTransaction,
603                 @NonNull TransitionFinishCallback finishCallback);
604 
605         /**
606          * Attempts to merge a different transition's animation into an animation that this handler
607          * is currently playing. If a merge is not possible/supported, this should be a no-op.
608          *
609          * This gets called if another transition becomes ready while this handler is still playing
610          * an animation. This is called regardless of whether this handler claims to support that
611          * particular transition or not.
612          *
613          * When this happens, there are 2 options:
614          *  1. Do nothing. This effectively rejects the merge request. This is the "safest" option.
615          *  2. Merge the incoming transition into this one. The implementation is up to this
616          *     handler. To indicate that this handler has "consumed" the merge transition, it
617          *     must call the finishCallback immediately, or at-least before the original
618          *     transition's finishCallback is called.
619          *
620          * @param transition This is the transition that wants to be merged.
621          * @param info Information about what is changing in the transition.
622          * @param t Contains surface changes that resulted from the transition.
623          * @param mergeTarget This is the transition that we are attempting to merge with (ie. the
624          *                    one this handler is currently already animating).
625          * @param finishCallback Call this if merged. This MUST be called on main thread.
626          */
mergeAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, @NonNull TransitionFinishCallback finishCallback)627         default void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
628                 @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
629                 @NonNull TransitionFinishCallback finishCallback) { }
630 
631         /**
632          * Potentially handles a startTransition request.
633          *
634          * @param transition The transition whose start is being requested.
635          * @param request Information about what is requested.
636          * @return WCT to apply with transition-start or null. If a WCT is returned here, this
637          *         handler will be the first in line to animate.
638          */
639         @Nullable
handleRequest(@onNull IBinder transition, @NonNull TransitionRequestInfo request)640         WindowContainerTransaction handleRequest(@NonNull IBinder transition,
641                 @NonNull TransitionRequestInfo request);
642 
643         /**
644          * Called when a transition which was already "claimed" by this handler has been merged
645          * into another animation. Gives this handler a chance to clean-up any expectations.
646          */
onTransitionMerged(@onNull IBinder transition)647         default void onTransitionMerged(@NonNull IBinder transition) { }
648 
649         /**
650          * Sets transition animation scale settings value to handler.
651          *
652          * @param scale The setting value of transition animation scale.
653          */
setAnimScaleSetting(float scale)654         default void setAnimScaleSetting(float scale) {}
655     }
656 
657     @BinderThread
658     private class TransitionPlayerImpl extends ITransitionPlayer.Stub {
659         @Override
onTransitionReady(IBinder iBinder, TransitionInfo transitionInfo, SurfaceControl.Transaction t, SurfaceControl.Transaction finishT)660         public void onTransitionReady(IBinder iBinder, TransitionInfo transitionInfo,
661                 SurfaceControl.Transaction t, SurfaceControl.Transaction finishT)
662                 throws RemoteException {
663             mMainExecutor.execute(() -> Transitions.this.onTransitionReady(
664                     iBinder, transitionInfo, t, finishT));
665         }
666 
667         @Override
requestStartTransition(IBinder iBinder, TransitionRequestInfo request)668         public void requestStartTransition(IBinder iBinder,
669                 TransitionRequestInfo request) throws RemoteException {
670             mMainExecutor.execute(() -> Transitions.this.requestStartTransition(iBinder, request));
671         }
672     }
673 
674     /**
675      * The interface for calls from outside the Shell, within the host process.
676      */
677     @ExternalThread
678     private class ShellTransitionImpl implements ShellTransitions {
679         private IShellTransitionsImpl mIShellTransitions;
680 
681         @Override
createExternalInterface()682         public IShellTransitions createExternalInterface() {
683             if (mIShellTransitions != null) {
684                 mIShellTransitions.invalidate();
685             }
686             mIShellTransitions = new IShellTransitionsImpl(Transitions.this);
687             return mIShellTransitions;
688         }
689 
690         @Override
registerRemote(@onNull TransitionFilter filter, @NonNull RemoteTransition remoteTransition)691         public void registerRemote(@NonNull TransitionFilter filter,
692                 @NonNull RemoteTransition remoteTransition) {
693             mMainExecutor.execute(() -> {
694                 mRemoteTransitionHandler.addFiltered(filter, remoteTransition);
695             });
696         }
697 
698         @Override
unregisterRemote(@onNull RemoteTransition remoteTransition)699         public void unregisterRemote(@NonNull RemoteTransition remoteTransition) {
700             mMainExecutor.execute(() -> {
701                 mRemoteTransitionHandler.removeFiltered(remoteTransition);
702             });
703         }
704     }
705 
706     /**
707      * The interface for calls from outside the host process.
708      */
709     @BinderThread
710     private static class IShellTransitionsImpl extends IShellTransitions.Stub {
711         private Transitions mTransitions;
712 
IShellTransitionsImpl(Transitions transitions)713         IShellTransitionsImpl(Transitions transitions) {
714             mTransitions = transitions;
715         }
716 
717         /**
718          * Invalidates this instance, preventing future calls from updating the controller.
719          */
invalidate()720         void invalidate() {
721             mTransitions = null;
722         }
723 
724         @Override
registerRemote(@onNull TransitionFilter filter, @NonNull RemoteTransition remoteTransition)725         public void registerRemote(@NonNull TransitionFilter filter,
726                 @NonNull RemoteTransition remoteTransition) {
727             executeRemoteCallWithTaskPermission(mTransitions, "registerRemote",
728                     (transitions) -> {
729                         transitions.mRemoteTransitionHandler.addFiltered(filter, remoteTransition);
730                     });
731         }
732 
733         @Override
unregisterRemote(@onNull RemoteTransition remoteTransition)734         public void unregisterRemote(@NonNull RemoteTransition remoteTransition) {
735             executeRemoteCallWithTaskPermission(mTransitions, "unregisterRemote",
736                     (transitions) -> {
737                         transitions.mRemoteTransitionHandler.removeFiltered(remoteTransition);
738                     });
739         }
740     }
741 
742     private class SettingsObserver extends ContentObserver {
743 
SettingsObserver()744         SettingsObserver() {
745             super(null);
746         }
747 
748         @Override
onChange(boolean selfChange)749         public void onChange(boolean selfChange) {
750             super.onChange(selfChange);
751             mTransitionAnimationScaleSetting = Settings.Global.getFloat(
752                     mContext.getContentResolver(), Settings.Global.TRANSITION_ANIMATION_SCALE,
753                     mTransitionAnimationScaleSetting);
754 
755             mMainExecutor.execute(() -> dispatchAnimScaleSetting(mTransitionAnimationScaleSetting));
756         }
757     }
758 }
759