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.splitscreen;
18 
19 import static android.view.WindowManager.TRANSIT_CHANGE;
20 import static android.view.WindowManager.TRANSIT_CLOSE;
21 import static android.view.WindowManager.TRANSIT_OPEN;
22 import static android.view.WindowManager.TRANSIT_TO_BACK;
23 
24 import static com.android.wm.shell.animation.Interpolators.ALPHA_IN;
25 import static com.android.wm.shell.animation.Interpolators.ALPHA_OUT;
26 import static com.android.wm.shell.common.split.SplitScreenConstants.FADE_DURATION;
27 import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
28 import static com.android.wm.shell.splitscreen.SplitScreen.stageTypeToString;
29 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER;
30 import static com.android.wm.shell.splitscreen.SplitScreenController.exitReasonToString;
31 import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_DISMISS;
32 import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_DISMISS_SNAP;
33 
34 import android.animation.Animator;
35 import android.animation.AnimatorListenerAdapter;
36 import android.animation.ValueAnimator;
37 import android.annotation.NonNull;
38 import android.annotation.Nullable;
39 import android.os.IBinder;
40 import android.view.SurfaceControl;
41 import android.view.WindowManager;
42 import android.window.RemoteTransition;
43 import android.window.TransitionInfo;
44 import android.window.WindowContainerToken;
45 import android.window.WindowContainerTransaction;
46 
47 import com.android.internal.protolog.common.ProtoLog;
48 import com.android.wm.shell.common.TransactionPool;
49 import com.android.wm.shell.common.split.SplitDecorManager;
50 import com.android.wm.shell.protolog.ShellProtoLogGroup;
51 import com.android.wm.shell.transition.OneShotRemoteHandler;
52 import com.android.wm.shell.transition.Transitions;
53 import com.android.wm.shell.util.TransitionUtil;
54 
55 import java.util.ArrayList;
56 
57 /** Manages transition animations for split-screen. */
58 class SplitScreenTransitions {
59     private static final String TAG = "SplitScreenTransitions";
60 
61     private final TransactionPool mTransactionPool;
62     private final Transitions mTransitions;
63     private final Runnable mOnFinish;
64 
65     DismissSession mPendingDismiss = null;
66     EnterSession mPendingEnter = null;
67     TransitSession mPendingResize = null;
68 
69     private IBinder mAnimatingTransition = null;
70     private OneShotRemoteHandler mActiveRemoteHandler = null;
71 
72     private final Transitions.TransitionFinishCallback mRemoteFinishCB = this::onFinish;
73 
74     /** Keeps track of currently running animations */
75     private final ArrayList<Animator> mAnimations = new ArrayList<>();
76     private final StageCoordinator mStageCoordinator;
77 
78     private Transitions.TransitionFinishCallback mFinishCallback = null;
79     private SurfaceControl.Transaction mFinishTransaction;
80 
SplitScreenTransitions(@onNull TransactionPool pool, @NonNull Transitions transitions, @NonNull Runnable onFinishCallback, StageCoordinator stageCoordinator)81     SplitScreenTransitions(@NonNull TransactionPool pool, @NonNull Transitions transitions,
82             @NonNull Runnable onFinishCallback, StageCoordinator stageCoordinator) {
83         mTransactionPool = pool;
84         mTransitions = transitions;
85         mOnFinish = onFinishCallback;
86         mStageCoordinator = stageCoordinator;
87     }
88 
initTransition(@onNull IBinder transition, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback)89     private void initTransition(@NonNull IBinder transition,
90             @NonNull SurfaceControl.Transaction finishTransaction,
91             @NonNull Transitions.TransitionFinishCallback finishCallback) {
92         mAnimatingTransition = transition;
93         mFinishTransaction = finishTransaction;
94         mFinishCallback = finishCallback;
95     }
96 
97     /** Play animation for enter transition or dismiss transition. */
playAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback, @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot, @NonNull WindowContainerToken topRoot)98     void playAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
99             @NonNull SurfaceControl.Transaction startTransaction,
100             @NonNull SurfaceControl.Transaction finishTransaction,
101             @NonNull Transitions.TransitionFinishCallback finishCallback,
102             @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot,
103             @NonNull WindowContainerToken topRoot) {
104         initTransition(transition, finishTransaction, finishCallback);
105 
106         final TransitSession pendingTransition = getPendingTransition(transition);
107         if (pendingTransition != null) {
108             if (pendingTransition.mCanceled) {
109                 // The pending transition was canceled, so skip playing animation.
110                 startTransaction.apply();
111                 onFinish(null /* wct */);
112                 return;
113             }
114 
115             if (pendingTransition.mRemoteHandler != null) {
116                 pendingTransition.mRemoteHandler.startAnimation(transition, info, startTransaction,
117                         finishTransaction, mRemoteFinishCB);
118                 mActiveRemoteHandler = pendingTransition.mRemoteHandler;
119                 return;
120             }
121         }
122 
123         playInternalAnimation(transition, info, startTransaction, mainRoot, sideRoot, topRoot);
124     }
125 
126     /** Internal funcation of playAnimation. */
playInternalAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot, @NonNull WindowContainerToken topRoot)127     private void playInternalAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
128             @NonNull SurfaceControl.Transaction t, @NonNull WindowContainerToken mainRoot,
129             @NonNull WindowContainerToken sideRoot, @NonNull WindowContainerToken topRoot) {
130         // Play some place-holder fade animations
131         final boolean isEnter = isPendingEnter(transition);
132         for (int i = info.getChanges().size() - 1; i >= 0; --i) {
133             final TransitionInfo.Change change = info.getChanges().get(i);
134             final SurfaceControl leash = change.getLeash();
135             final int mode = info.getChanges().get(i).getMode();
136 
137             final int rootIdx = TransitionUtil.rootIndexFor(change, info);
138             if (mode == TRANSIT_CHANGE) {
139                 if (change.getParent() != null) {
140                     // This is probably reparented, so we want the parent to be immediately visible
141                     final TransitionInfo.Change parentChange = info.getChange(change.getParent());
142                     t.show(parentChange.getLeash());
143                     t.setAlpha(parentChange.getLeash(), 1.f);
144                     // and then animate this layer outside the parent (since, for example, this is
145                     // the home task animating from fullscreen to part-screen).
146                     t.reparent(parentChange.getLeash(), info.getRoot(rootIdx).getLeash());
147                     t.setLayer(parentChange.getLeash(), info.getChanges().size() - i);
148                     // build the finish reparent/reposition
149                     mFinishTransaction.reparent(leash, parentChange.getLeash());
150                     mFinishTransaction.setPosition(leash,
151                             change.getEndRelOffset().x, change.getEndRelOffset().y);
152                 }
153             }
154 
155             final boolean isTopRoot = topRoot.equals(change.getContainer());
156             final boolean isMainRoot = mainRoot.equals(change.getContainer());
157             final boolean isSideRoot = sideRoot.equals(change.getContainer());
158             final boolean isDivider = change.getFlags() == FLAG_IS_DIVIDER_BAR;
159             final boolean isMainChild = mainRoot.equals(change.getParent());
160             final boolean isSideChild = sideRoot.equals(change.getParent());
161             if (isEnter && (isMainChild || isSideChild)) {
162                 // Reset child tasks bounds on finish.
163                 mFinishTransaction.setPosition(leash,
164                         change.getEndRelOffset().x, change.getEndRelOffset().y);
165                 mFinishTransaction.setCrop(leash, null);
166             } else if (isTopRoot) {
167                 // Ensure top root is visible at start.
168                 t.setAlpha(leash, 1.f);
169                 t.show(leash);
170             } else if (isEnter && isMainRoot || isSideRoot) {
171                 t.setPosition(leash, change.getEndAbsBounds().left, change.getEndAbsBounds().top);
172                 t.setWindowCrop(leash, change.getEndAbsBounds().width(),
173                         change.getEndAbsBounds().height());
174             } else if (isDivider) {
175                 t.setPosition(leash, change.getEndAbsBounds().left, change.getEndAbsBounds().top);
176                 t.setLayer(leash, Integer.MAX_VALUE);
177                 t.show(leash);
178             }
179 
180             // We want to use child tasks to animate so ignore split root container and non task
181             // except divider change.
182             if (isTopRoot || isMainRoot || isSideRoot
183                     || (change.getTaskInfo() == null && !isDivider)) {
184                 continue;
185             }
186             if (isEnter && mPendingEnter.mResizeAnim) {
187                 // We will run animation in next transition so skip anim here
188                 continue;
189             } else if (isPendingDismiss(transition)
190                     && mPendingDismiss.mReason == EXIT_REASON_DRAG_DIVIDER) {
191                 // TODO(b/280020345): need to refine animation for this but just skip anim now.
192                 continue;
193             }
194 
195             // Because cross fade might be looked more flicker during animation
196             // (surface become black in middle of animation), we only do fade-out
197             // and show opening surface directly.
198             boolean isOpening = TransitionUtil.isOpeningType(info.getType());
199             if (!isOpening && (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK)) {
200                 // fade out
201                 startFadeAnimation(leash, false /* show */);
202             } else if (mode == TRANSIT_CHANGE && change.getSnapshot() != null) {
203                 t.reparent(change.getSnapshot(), info.getRoot(rootIdx).getLeash());
204                 // Ensure snapshot it on the top of all transition surfaces
205                 t.setLayer(change.getSnapshot(), info.getChanges().size() + 1);
206                 t.setPosition(change.getSnapshot(), change.getStartAbsBounds().left,
207                         change.getStartAbsBounds().top);
208                 t.show(change.getSnapshot());
209                 startFadeAnimation(change.getSnapshot(), false /* show */);
210             }
211         }
212         t.apply();
213         onFinish(null /* wct */);
214     }
215 
216     /** Play animation for drag divider dismiss transition. */
playDragDismissAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback, @NonNull WindowContainerToken toTopRoot, @NonNull SplitDecorManager toTopDecor, @NonNull WindowContainerToken topRoot)217     void playDragDismissAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
218             @NonNull SurfaceControl.Transaction startTransaction,
219             @NonNull SurfaceControl.Transaction finishTransaction,
220             @NonNull Transitions.TransitionFinishCallback finishCallback,
221             @NonNull WindowContainerToken toTopRoot, @NonNull SplitDecorManager toTopDecor,
222             @NonNull WindowContainerToken topRoot) {
223         initTransition(transition, finishTransaction, finishCallback);
224 
225         for (int i = info.getChanges().size() - 1; i >= 0; --i) {
226             final TransitionInfo.Change change = info.getChanges().get(i);
227             final SurfaceControl leash = change.getLeash();
228 
229             if (toTopRoot.equals(change.getContainer())) {
230                 startTransaction.setAlpha(leash, 1.f);
231                 startTransaction.show(leash);
232 
233                 ValueAnimator va = new ValueAnimator();
234                 mAnimations.add(va);
235 
236                 toTopDecor.onResized(startTransaction, animated -> {
237                     mAnimations.remove(va);
238                     if (animated) {
239                         mTransitions.getMainExecutor().execute(() -> {
240                             onFinish(null /* wct */);
241                         });
242                     }
243                 });
244             } else if (topRoot.equals(change.getContainer())) {
245                 // Ensure it on top of all changes in transition.
246                 startTransaction.setLayer(leash, Integer.MAX_VALUE);
247                 startTransaction.setAlpha(leash, 1.f);
248                 startTransaction.show(leash);
249             }
250         }
251         startTransaction.apply();
252         onFinish(null /* wct */);
253     }
254 
255     /** Play animation for resize transition. */
playResizeAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback, @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot, @NonNull SplitDecorManager mainDecor, @NonNull SplitDecorManager sideDecor)256     void playResizeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
257             @NonNull SurfaceControl.Transaction startTransaction,
258             @NonNull SurfaceControl.Transaction finishTransaction,
259             @NonNull Transitions.TransitionFinishCallback finishCallback,
260             @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot,
261             @NonNull SplitDecorManager mainDecor, @NonNull SplitDecorManager sideDecor) {
262         initTransition(transition, finishTransaction, finishCallback);
263 
264         for (int i = info.getChanges().size() - 1; i >= 0; --i) {
265             final TransitionInfo.Change change = info.getChanges().get(i);
266             if (mainRoot.equals(change.getContainer()) || sideRoot.equals(change.getContainer())) {
267                 final SurfaceControl leash = change.getLeash();
268                 startTransaction.setPosition(leash, change.getEndAbsBounds().left,
269                         change.getEndAbsBounds().top);
270                 startTransaction.setWindowCrop(leash, change.getEndAbsBounds().width(),
271                         change.getEndAbsBounds().height());
272 
273                 SplitDecorManager decor = mainRoot.equals(change.getContainer())
274                         ? mainDecor : sideDecor;
275 
276                 // This is to ensure onFinished be called after all animations ended.
277                 ValueAnimator va = new ValueAnimator();
278                 mAnimations.add(va);
279 
280                 decor.setScreenshotIfNeeded(change.getSnapshot(), startTransaction);
281                 decor.onResized(startTransaction, animated -> {
282                     mAnimations.remove(va);
283                     if (animated) {
284                         mTransitions.getMainExecutor().execute(() -> {
285                             onFinish(null /* wct */);
286                         });
287                     }
288                 });
289             }
290         }
291 
292         startTransaction.apply();
293         onFinish(null /* wct */);
294     }
295 
isPendingTransition(IBinder transition)296     boolean isPendingTransition(IBinder transition) {
297         return getPendingTransition(transition) != null;
298     }
299 
isPendingEnter(IBinder transition)300     boolean isPendingEnter(IBinder transition) {
301         return mPendingEnter != null && mPendingEnter.mTransition == transition;
302     }
303 
isPendingDismiss(IBinder transition)304     boolean isPendingDismiss(IBinder transition) {
305         return mPendingDismiss != null && mPendingDismiss.mTransition == transition;
306     }
307 
isPendingResize(IBinder transition)308     boolean isPendingResize(IBinder transition) {
309         return mPendingResize != null && mPendingResize.mTransition == transition;
310     }
311 
312     @Nullable
getPendingTransition(IBinder transition)313     private TransitSession getPendingTransition(IBinder transition) {
314         if (isPendingEnter(transition)) {
315             return mPendingEnter;
316         } else if (isPendingDismiss(transition)) {
317             return mPendingDismiss;
318         } else if (isPendingResize(transition)) {
319             return mPendingResize;
320         }
321 
322         return null;
323     }
324 
startFullscreenTransition(WindowContainerTransaction wct, @Nullable RemoteTransition handler)325     void startFullscreenTransition(WindowContainerTransaction wct,
326             @Nullable RemoteTransition handler) {
327         OneShotRemoteHandler fullscreenHandler =
328                 new OneShotRemoteHandler(mTransitions.getMainExecutor(), handler);
329         fullscreenHandler.setTransition(mTransitions
330                 .startTransition(TRANSIT_OPEN, wct, fullscreenHandler));
331     }
332 
333 
334     /** Starts a transition to enter split with a remote transition animator. */
startEnterTransition( @indowManager.TransitionType int transitType, WindowContainerTransaction wct, @Nullable RemoteTransition remoteTransition, Transitions.TransitionHandler handler, int extraTransitType, boolean resizeAnim)335     IBinder startEnterTransition(
336             @WindowManager.TransitionType int transitType,
337             WindowContainerTransaction wct,
338             @Nullable RemoteTransition remoteTransition,
339             Transitions.TransitionHandler handler,
340             int extraTransitType, boolean resizeAnim) {
341         if (mPendingEnter != null) {
342             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "  splitTransition "
343                     + " skip to start enter split transition since it already exist. ");
344             return null;
345         }
346         final IBinder transition = mTransitions.startTransition(transitType, wct, handler);
347         setEnterTransition(transition, remoteTransition, extraTransitType, resizeAnim);
348         return transition;
349     }
350 
351     /** Sets a transition to enter split. */
setEnterTransition(@onNull IBinder transition, @Nullable RemoteTransition remoteTransition, int extraTransitType, boolean resizeAnim)352     void setEnterTransition(@NonNull IBinder transition,
353             @Nullable RemoteTransition remoteTransition,
354             int extraTransitType, boolean resizeAnim) {
355         mPendingEnter = new EnterSession(
356                 transition, remoteTransition, extraTransitType, resizeAnim);
357 
358         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "  splitTransition "
359                 + " deduced Enter split screen");
360     }
361 
362     /** Starts a transition to dismiss split. */
startDismissTransition(WindowContainerTransaction wct, Transitions.TransitionHandler handler, @SplitScreen.StageType int dismissTop, @SplitScreenController.ExitReason int reason)363     IBinder startDismissTransition(WindowContainerTransaction wct,
364             Transitions.TransitionHandler handler, @SplitScreen.StageType int dismissTop,
365             @SplitScreenController.ExitReason int reason) {
366         if (mPendingDismiss != null) {
367             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "  splitTransition "
368                     + " skip to start dismiss split transition since it already exist. reason to "
369                     + " dismiss = %s", exitReasonToString(reason));
370             return null;
371         }
372         final int type = reason == EXIT_REASON_DRAG_DIVIDER
373                 ? TRANSIT_SPLIT_DISMISS_SNAP : TRANSIT_SPLIT_DISMISS;
374         IBinder transition = mTransitions.startTransition(type, wct, handler);
375         setDismissTransition(transition, dismissTop, reason);
376         return transition;
377     }
378 
379     /** Sets a transition to dismiss split. */
setDismissTransition(@onNull IBinder transition, @SplitScreen.StageType int dismissTop, @SplitScreenController.ExitReason int reason)380     void setDismissTransition(@NonNull IBinder transition, @SplitScreen.StageType int dismissTop,
381             @SplitScreenController.ExitReason int reason) {
382         mPendingDismiss = new DismissSession(transition, reason, dismissTop);
383 
384         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "  splitTransition "
385                         + " deduced Dismiss due to %s. toTop=%s",
386                 exitReasonToString(reason), stageTypeToString(dismissTop));
387     }
388 
startResizeTransition(WindowContainerTransaction wct, Transitions.TransitionHandler handler, @Nullable TransitionFinishedCallback finishCallback)389     IBinder startResizeTransition(WindowContainerTransaction wct,
390             Transitions.TransitionHandler handler,
391             @Nullable TransitionFinishedCallback finishCallback) {
392         if (mPendingResize != null) {
393             mPendingResize.cancel(null);
394             mAnimations.clear();
395             onFinish(null /* wct */);
396         }
397 
398         IBinder transition = mTransitions.startTransition(TRANSIT_CHANGE, wct, handler);
399         setResizeTransition(transition, finishCallback);
400         return transition;
401     }
402 
setResizeTransition(@onNull IBinder transition, @Nullable TransitionFinishedCallback finishCallback)403     void setResizeTransition(@NonNull IBinder transition,
404             @Nullable TransitionFinishedCallback finishCallback) {
405         mPendingResize = new TransitSession(transition, null /* consumedCb */, finishCallback);
406         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "  splitTransition "
407                 + " deduced Resize split screen");
408     }
409 
mergeAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t, IBinder mergeTarget, Transitions.TransitionFinishCallback finishCallback)410     void mergeAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t,
411             IBinder mergeTarget, Transitions.TransitionFinishCallback finishCallback) {
412         if (mergeTarget != mAnimatingTransition) return;
413 
414         if (mActiveRemoteHandler != null) {
415             mActiveRemoteHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
416         } else {
417             for (int i = mAnimations.size() - 1; i >= 0; --i) {
418                 final Animator anim = mAnimations.get(i);
419                 mTransitions.getAnimExecutor().execute(anim::end);
420             }
421         }
422     }
423 
end()424     boolean end() {
425         // If It's remote, there's nothing we can do right now.
426         if (mActiveRemoteHandler != null) return false;
427         for (int i = mAnimations.size() - 1; i >= 0; --i) {
428             final Animator anim = mAnimations.get(i);
429             mTransitions.getAnimExecutor().execute(anim::end);
430         }
431         return true;
432     }
433 
onTransitionConsumed(@onNull IBinder transition, boolean aborted, @Nullable SurfaceControl.Transaction finishT)434     void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
435             @Nullable SurfaceControl.Transaction finishT) {
436         if (isPendingEnter(transition)) {
437             if (!aborted) {
438                 // An entering transition got merged, appends the rest operations to finish entering
439                 // split screen.
440                 mStageCoordinator.finishEnterSplitScreen(finishT);
441             }
442 
443             mPendingEnter.onConsumed(aborted);
444             mPendingEnter = null;
445         } else if (isPendingDismiss(transition)) {
446             mPendingDismiss.onConsumed(aborted);
447             mPendingDismiss = null;
448         } else if (isPendingResize(transition)) {
449             mPendingResize.onConsumed(aborted);
450             mPendingResize = null;
451         }
452     }
453 
onFinish(WindowContainerTransaction wct)454     void onFinish(WindowContainerTransaction wct) {
455         if (!mAnimations.isEmpty()) return;
456 
457         if (wct == null) wct = new WindowContainerTransaction();
458         if (isPendingEnter(mAnimatingTransition)) {
459             mPendingEnter.onFinished(wct, mFinishTransaction);
460             mPendingEnter = null;
461         } else if (isPendingDismiss(mAnimatingTransition)) {
462             mPendingDismiss.onFinished(wct, mFinishTransaction);
463             mPendingDismiss = null;
464         } else if (isPendingResize(mAnimatingTransition)) {
465             mPendingResize.onFinished(wct, mFinishTransaction);
466             mPendingResize = null;
467         }
468 
469         mActiveRemoteHandler = null;
470         mAnimatingTransition = null;
471 
472         mOnFinish.run();
473         if (mFinishCallback != null) {
474             mFinishCallback.onTransitionFinished(wct /* wct */);
475             mFinishCallback = null;
476         }
477     }
478 
startFadeAnimation(@onNull SurfaceControl leash, boolean show)479     private void startFadeAnimation(@NonNull SurfaceControl leash, boolean show) {
480         final float end = show ? 1.f : 0.f;
481         final float start = 1.f - end;
482         final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
483         final ValueAnimator va = ValueAnimator.ofFloat(start, end);
484         va.setDuration(FADE_DURATION);
485         va.setInterpolator(show ? ALPHA_IN : ALPHA_OUT);
486         va.addUpdateListener(animation -> {
487             float fraction = animation.getAnimatedFraction();
488             transaction.setAlpha(leash, start * (1.f - fraction) + end * fraction);
489             transaction.apply();
490         });
491         va.addListener(new AnimatorListenerAdapter() {
492             @Override
493             public void onAnimationEnd(Animator animation) {
494                 transaction.setAlpha(leash, end);
495                 transaction.apply();
496                 mTransactionPool.release(transaction);
497                 mTransitions.getMainExecutor().execute(() -> {
498                     mAnimations.remove(va);
499                     onFinish(null /* wct */);
500                 });
501             }
502         });
503         mAnimations.add(va);
504         mTransitions.getAnimExecutor().execute(va::start);
505     }
506 
507     /** Calls when the transition got consumed. */
508     interface TransitionConsumedCallback {
onConsumed(boolean aborted)509         void onConsumed(boolean aborted);
510     }
511 
512     /** Calls when the transition finished. */
513     interface TransitionFinishedCallback {
onFinished(WindowContainerTransaction wct, SurfaceControl.Transaction t)514         void onFinished(WindowContainerTransaction wct, SurfaceControl.Transaction t);
515     }
516 
517     /** Session for a transition and its clean-up callback. */
518     class TransitSession {
519         final IBinder mTransition;
520         TransitionConsumedCallback mConsumedCallback;
521         TransitionFinishedCallback mFinishedCallback;
522         OneShotRemoteHandler mRemoteHandler;
523 
524         /** Whether the transition was canceled. */
525         boolean mCanceled;
526 
527         /** A note for extra transit type, to help indicate custom transition. */
528         final int mExtraTransitType;
529 
TransitSession(IBinder transition, @Nullable TransitionConsumedCallback consumedCallback, @Nullable TransitionFinishedCallback finishedCallback)530         TransitSession(IBinder transition,
531                 @Nullable TransitionConsumedCallback consumedCallback,
532                 @Nullable TransitionFinishedCallback finishedCallback) {
533             this(transition, consumedCallback, finishedCallback, null /* remoteTransition */, 0);
534         }
535 
TransitSession(IBinder transition, @Nullable TransitionConsumedCallback consumedCallback, @Nullable TransitionFinishedCallback finishedCallback, @Nullable RemoteTransition remoteTransition, int extraTransitType)536         TransitSession(IBinder transition,
537                 @Nullable TransitionConsumedCallback consumedCallback,
538                 @Nullable TransitionFinishedCallback finishedCallback,
539                 @Nullable RemoteTransition remoteTransition, int extraTransitType) {
540             mTransition = transition;
541             mConsumedCallback = consumedCallback;
542             mFinishedCallback = finishedCallback;
543 
544             if (remoteTransition != null) {
545                 // Wrapping the remote transition for ease-of-use. (OneShot handles all the binder
546                 // linking/death stuff)
547                 mRemoteHandler = new OneShotRemoteHandler(
548                         mTransitions.getMainExecutor(), remoteTransition);
549                 mRemoteHandler.setTransition(transition);
550             }
551             mExtraTransitType = extraTransitType;
552         }
553 
554         /** Sets transition consumed callback. */
setConsumedCallback(@ullable TransitionConsumedCallback callback)555         void setConsumedCallback(@Nullable TransitionConsumedCallback callback) {
556             mConsumedCallback = callback;
557         }
558 
559         /** Sets transition finished callback. */
setFinishedCallback(@ullable TransitionFinishedCallback callback)560         void setFinishedCallback(@Nullable TransitionFinishedCallback callback) {
561             mFinishedCallback = callback;
562         }
563 
564         /**
565          * Cancels the transition. This should be called before playing animation. A canceled
566          * transition will skip playing animation.
567          *
568          * @param finishedCb new finish callback to override.
569          */
cancel(@ullable TransitionFinishedCallback finishedCb)570         void cancel(@Nullable TransitionFinishedCallback finishedCb) {
571             mCanceled = true;
572             setFinishedCallback(finishedCb);
573         }
574 
onConsumed(boolean aborted)575         void onConsumed(boolean aborted) {
576             if (mConsumedCallback != null) {
577                 mConsumedCallback.onConsumed(aborted);
578             }
579         }
580 
onFinished(WindowContainerTransaction finishWct, SurfaceControl.Transaction finishT)581         void onFinished(WindowContainerTransaction finishWct,
582                 SurfaceControl.Transaction finishT) {
583             if (mFinishedCallback != null) {
584                 mFinishedCallback.onFinished(finishWct, finishT);
585             }
586         }
587     }
588 
589     /** Bundled information of enter transition. */
590     class EnterSession extends TransitSession {
591         final boolean mResizeAnim;
592 
EnterSession(IBinder transition, @Nullable RemoteTransition remoteTransition, int extraTransitType, boolean resizeAnim)593         EnterSession(IBinder transition,
594                 @Nullable RemoteTransition remoteTransition,
595                 int extraTransitType, boolean resizeAnim) {
596             super(transition, null /* consumedCallback */, null /* finishedCallback */,
597                     remoteTransition, extraTransitType);
598             this.mResizeAnim = resizeAnim;
599         }
600     }
601 
602     /** Bundled information of dismiss transition. */
603     class DismissSession extends TransitSession {
604         final int mReason;
605         final @SplitScreen.StageType int mDismissTop;
606 
DismissSession(IBinder transition, int reason, int dismissTop)607         DismissSession(IBinder transition, int reason, int dismissTop) {
608             super(transition, null /* consumedCallback */, null /* finishedCallback */);
609             this.mReason = reason;
610             this.mDismissTop = dismissTop;
611         }
612     }
613 }
614