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