• 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.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 import static android.view.WindowManager.TRANSIT_TO_FRONT;
24 
25 import static com.android.wm.shell.splitscreen.SplitScreen.stageTypeToString;
26 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER;
27 import static com.android.wm.shell.splitscreen.SplitScreenController.exitReasonToString;
28 import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_DISMISS;
29 import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_DISMISS_SNAP;
30 import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE;
31 import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
32 
33 import android.animation.Animator;
34 import android.animation.AnimatorListenerAdapter;
35 import android.animation.ValueAnimator;
36 import android.annotation.NonNull;
37 import android.annotation.Nullable;
38 import android.graphics.Rect;
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 import android.window.WindowContainerTransactionCallback;
47 
48 import com.android.internal.protolog.common.ProtoLog;
49 import com.android.wm.shell.common.TransactionPool;
50 import com.android.wm.shell.common.split.SplitDecorManager;
51 import com.android.wm.shell.protolog.ShellProtoLogGroup;
52 import com.android.wm.shell.transition.OneShotRemoteHandler;
53 import com.android.wm.shell.transition.Transitions;
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     DismissTransition mPendingDismiss = null;
66     TransitSession mPendingEnter = null;
67     TransitSession mPendingRecent = null;
68     TransitSession mPendingResize = null;
69 
70     private IBinder mAnimatingTransition = null;
71     OneShotRemoteHandler mPendingRemoteHandler = null;
72     private OneShotRemoteHandler mActiveRemoteHandler = null;
73 
74     private final Transitions.TransitionFinishCallback mRemoteFinishCB = this::onFinish;
75 
76     /** Keeps track of currently running animations */
77     private final ArrayList<Animator> mAnimations = new ArrayList<>();
78     private final StageCoordinator mStageCoordinator;
79 
80     private Transitions.TransitionFinishCallback mFinishCallback = null;
81     private SurfaceControl.Transaction mFinishTransaction;
82 
SplitScreenTransitions(@onNull TransactionPool pool, @NonNull Transitions transitions, @NonNull Runnable onFinishCallback, StageCoordinator stageCoordinator)83     SplitScreenTransitions(@NonNull TransactionPool pool, @NonNull Transitions transitions,
84             @NonNull Runnable onFinishCallback, StageCoordinator stageCoordinator) {
85         mTransactionPool = pool;
86         mTransitions = transitions;
87         mOnFinish = onFinishCallback;
88         mStageCoordinator = stageCoordinator;
89     }
90 
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)91     void playAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
92             @NonNull SurfaceControl.Transaction startTransaction,
93             @NonNull SurfaceControl.Transaction finishTransaction,
94             @NonNull Transitions.TransitionFinishCallback finishCallback,
95             @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot,
96             @NonNull WindowContainerToken topRoot) {
97         mFinishCallback = finishCallback;
98         mAnimatingTransition = transition;
99         mFinishTransaction = finishTransaction;
100         if (mPendingRemoteHandler != null) {
101             mPendingRemoteHandler.startAnimation(transition, info, startTransaction,
102                     finishTransaction, mRemoteFinishCB);
103             mActiveRemoteHandler = mPendingRemoteHandler;
104             mPendingRemoteHandler = null;
105             return;
106         }
107         playInternalAnimation(transition, info, startTransaction, mainRoot, sideRoot, topRoot);
108     }
109 
playInternalAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot, @NonNull WindowContainerToken topRoot)110     private void playInternalAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
111             @NonNull SurfaceControl.Transaction t, @NonNull WindowContainerToken mainRoot,
112             @NonNull WindowContainerToken sideRoot, @NonNull WindowContainerToken topRoot) {
113         final TransitSession pendingTransition = getPendingTransition(transition);
114         if (pendingTransition != null && pendingTransition.mCanceled) {
115             // The pending transition was canceled, so skip playing animation.
116             t.apply();
117             onFinish(null /* wct */, null /* wctCB */);
118             return;
119         }
120 
121         // Play some place-holder fade animations
122         for (int i = info.getChanges().size() - 1; i >= 0; --i) {
123             final TransitionInfo.Change change = info.getChanges().get(i);
124             final SurfaceControl leash = change.getLeash();
125             final int mode = info.getChanges().get(i).getMode();
126 
127             if (mode == TRANSIT_CHANGE) {
128                 if (change.getParent() != null) {
129                     // This is probably reparented, so we want the parent to be immediately visible
130                     final TransitionInfo.Change parentChange = info.getChange(change.getParent());
131                     t.show(parentChange.getLeash());
132                     t.setAlpha(parentChange.getLeash(), 1.f);
133                     // and then animate this layer outside the parent (since, for example, this is
134                     // the home task animating from fullscreen to part-screen).
135                     t.reparent(leash, info.getRootLeash());
136                     t.setLayer(leash, info.getChanges().size() - i);
137                     // build the finish reparent/reposition
138                     mFinishTransaction.reparent(leash, parentChange.getLeash());
139                     mFinishTransaction.setPosition(leash,
140                             change.getEndRelOffset().x, change.getEndRelOffset().y);
141                 }
142                 // TODO(shell-transitions): screenshot here
143                 final Rect startBounds = new Rect(change.getStartAbsBounds());
144                 final Rect endBounds = new Rect(change.getEndAbsBounds());
145                 startBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y);
146                 endBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y);
147                 startExampleResizeAnimation(leash, startBounds, endBounds);
148             }
149             boolean isRootOrSplitSideRoot = change.getParent() == null
150                     || topRoot.equals(change.getParent());
151             // For enter or exit, we only want to animate the side roots but not the top-root.
152             if (!isRootOrSplitSideRoot || topRoot.equals(change.getContainer())) {
153                 continue;
154             }
155 
156             if (isPendingEnter(transition) && (mainRoot.equals(change.getContainer())
157                     || sideRoot.equals(change.getContainer()))) {
158                 t.setPosition(leash, change.getEndAbsBounds().left, change.getEndAbsBounds().top);
159                 t.setWindowCrop(leash, change.getEndAbsBounds().width(),
160                         change.getEndAbsBounds().height());
161             }
162             boolean isOpening = isOpeningTransition(info);
163             if (isOpening && (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT)) {
164                 // fade in
165                 startExampleAnimation(leash, true /* show */);
166             } else if (!isOpening && (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK)) {
167                 // fade out
168                 if (info.getType() == TRANSIT_SPLIT_DISMISS_SNAP) {
169                     // Dismissing via snap-to-top/bottom means that the dismissed task is already
170                     // not-visible (usually cropped to oblivion) so immediately set its alpha to 0
171                     // and don't animate it so it doesn't pop-in when reparented.
172                     t.setAlpha(leash, 0.f);
173                 } else {
174                     startExampleAnimation(leash, false /* show */);
175                 }
176             }
177         }
178         t.apply();
179         onFinish(null /* wct */, null /* wctCB */);
180     }
181 
applyResizeTransition(@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)182     void applyResizeTransition(@NonNull IBinder transition, @NonNull TransitionInfo info,
183             @NonNull SurfaceControl.Transaction startTransaction,
184             @NonNull SurfaceControl.Transaction finishTransaction,
185             @NonNull Transitions.TransitionFinishCallback finishCallback,
186             @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot,
187             @NonNull SplitDecorManager mainDecor, @NonNull SplitDecorManager sideDecor) {
188         mFinishCallback = finishCallback;
189         mAnimatingTransition = transition;
190         mFinishTransaction = finishTransaction;
191 
192         for (int i = info.getChanges().size() - 1; i >= 0; --i) {
193             final TransitionInfo.Change change = info.getChanges().get(i);
194             if (mainRoot.equals(change.getContainer()) || sideRoot.equals(change.getContainer())) {
195                 final SurfaceControl leash = change.getLeash();
196                 startTransaction.setPosition(leash, change.getEndAbsBounds().left,
197                         change.getEndAbsBounds().top);
198                 startTransaction.setWindowCrop(leash, change.getEndAbsBounds().width(),
199                         change.getEndAbsBounds().height());
200 
201                 SplitDecorManager decor = mainRoot.equals(change.getContainer())
202                         ? mainDecor : sideDecor;
203                 ValueAnimator va = new ValueAnimator();
204                 mAnimations.add(va);
205                 decor.setScreenshotIfNeeded(change.getSnapshot(), startTransaction);
206                 decor.onResized(startTransaction, () -> {
207                     mTransitions.getMainExecutor().execute(() -> {
208                         mAnimations.remove(va);
209                         onFinish(null /* wct */, null /* wctCB */);
210                     });
211                 });
212             }
213         }
214 
215         startTransaction.apply();
216         onFinish(null /* wct */, null /* wctCB */);
217     }
218 
isPendingTransition(IBinder transition)219     boolean isPendingTransition(IBinder transition) {
220         return getPendingTransition(transition) != null;
221     }
222 
isPendingEnter(IBinder transition)223     boolean isPendingEnter(IBinder transition) {
224         return mPendingEnter != null && mPendingEnter.mTransition == transition;
225     }
226 
isPendingRecent(IBinder transition)227     boolean isPendingRecent(IBinder transition) {
228         return mPendingRecent != null && mPendingRecent.mTransition == transition;
229     }
230 
isPendingDismiss(IBinder transition)231     boolean isPendingDismiss(IBinder transition) {
232         return mPendingDismiss != null && mPendingDismiss.mTransition == transition;
233     }
234 
isPendingResize(IBinder transition)235     boolean isPendingResize(IBinder transition) {
236         return mPendingResize != null && mPendingResize.mTransition == transition;
237     }
238 
239     @Nullable
getPendingTransition(IBinder transition)240     private TransitSession getPendingTransition(IBinder transition) {
241         if (isPendingEnter(transition)) {
242             return mPendingEnter;
243         } else if (isPendingRecent(transition)) {
244             return mPendingRecent;
245         } else if (isPendingDismiss(transition)) {
246             return mPendingDismiss;
247         } else if (isPendingResize(transition)) {
248             return mPendingResize;
249         }
250 
251         return null;
252     }
253 
254 
255     /** Starts a transition to enter split with a remote transition animator. */
startEnterTransition( @indowManager.TransitionType int transitType, WindowContainerTransaction wct, @Nullable RemoteTransition remoteTransition, Transitions.TransitionHandler handler, @Nullable TransitionConsumedCallback consumedCallback, @Nullable TransitionFinishedCallback finishedCallback)256     IBinder startEnterTransition(
257             @WindowManager.TransitionType int transitType,
258             WindowContainerTransaction wct,
259             @Nullable RemoteTransition remoteTransition,
260             Transitions.TransitionHandler handler,
261             @Nullable TransitionConsumedCallback consumedCallback,
262             @Nullable TransitionFinishedCallback finishedCallback) {
263         final IBinder transition = mTransitions.startTransition(transitType, wct, handler);
264         setEnterTransition(transition, remoteTransition, consumedCallback, finishedCallback);
265         return transition;
266     }
267 
268     /** Sets a transition to enter split. */
setEnterTransition(@onNull IBinder transition, @Nullable RemoteTransition remoteTransition, @Nullable TransitionConsumedCallback consumedCallback, @Nullable TransitionFinishedCallback finishedCallback)269     void setEnterTransition(@NonNull IBinder transition,
270             @Nullable RemoteTransition remoteTransition,
271             @Nullable TransitionConsumedCallback consumedCallback,
272             @Nullable TransitionFinishedCallback finishedCallback) {
273         mPendingEnter = new TransitSession(transition, consumedCallback, finishedCallback);
274 
275         if (remoteTransition != null) {
276             // Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff)
277             mPendingRemoteHandler = new OneShotRemoteHandler(
278                     mTransitions.getMainExecutor(), remoteTransition);
279             mPendingRemoteHandler.setTransition(transition);
280         }
281 
282         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "  splitTransition "
283                 + " deduced Enter split screen");
284     }
285 
286     /** Starts a transition to dismiss split. */
startDismissTransition(WindowContainerTransaction wct, Transitions.TransitionHandler handler, @SplitScreen.StageType int dismissTop, @SplitScreenController.ExitReason int reason)287     IBinder startDismissTransition(WindowContainerTransaction wct,
288             Transitions.TransitionHandler handler, @SplitScreen.StageType int dismissTop,
289             @SplitScreenController.ExitReason int reason) {
290         final int type = reason == EXIT_REASON_DRAG_DIVIDER
291                 ? TRANSIT_SPLIT_DISMISS_SNAP : TRANSIT_SPLIT_DISMISS;
292         IBinder transition = mTransitions.startTransition(type, wct, handler);
293         setDismissTransition(transition, dismissTop, reason);
294         return transition;
295     }
296 
297     /** Sets a transition to dismiss split. */
setDismissTransition(@onNull IBinder transition, @SplitScreen.StageType int dismissTop, @SplitScreenController.ExitReason int reason)298     void setDismissTransition(@NonNull IBinder transition, @SplitScreen.StageType int dismissTop,
299             @SplitScreenController.ExitReason int reason) {
300         mPendingDismiss = new DismissTransition(transition, reason, dismissTop);
301 
302         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "  splitTransition "
303                         + " deduced Dismiss due to %s. toTop=%s",
304                 exitReasonToString(reason), stageTypeToString(dismissTop));
305     }
306 
startResizeTransition(WindowContainerTransaction wct, Transitions.TransitionHandler handler, @Nullable TransitionFinishedCallback finishCallback)307     IBinder startResizeTransition(WindowContainerTransaction wct,
308             Transitions.TransitionHandler handler,
309             @Nullable TransitionFinishedCallback finishCallback) {
310         IBinder transition = mTransitions.startTransition(TRANSIT_CHANGE, wct, handler);
311         setResizeTransition(transition, finishCallback);
312         return transition;
313     }
314 
setResizeTransition(@onNull IBinder transition, @Nullable TransitionFinishedCallback finishCallback)315     void setResizeTransition(@NonNull IBinder transition,
316             @Nullable TransitionFinishedCallback finishCallback) {
317         mPendingResize = new TransitSession(transition, null /* consumedCb */, finishCallback);
318         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "  splitTransition "
319                 + " deduced Resize split screen");
320     }
321 
setRecentTransition(@onNull IBinder transition, @Nullable RemoteTransition remoteTransition, @Nullable TransitionFinishedCallback finishCallback)322     void setRecentTransition(@NonNull IBinder transition,
323             @Nullable RemoteTransition remoteTransition,
324             @Nullable TransitionFinishedCallback finishCallback) {
325         mPendingRecent = new TransitSession(transition, null /* consumedCb */, finishCallback);
326 
327         if (remoteTransition != null) {
328             // Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff)
329             mPendingRemoteHandler = new OneShotRemoteHandler(
330                     mTransitions.getMainExecutor(), remoteTransition);
331             mPendingRemoteHandler.setTransition(transition);
332         }
333 
334         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "  splitTransition "
335                 + " deduced Enter recent panel");
336     }
337 
mergeAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t, IBinder mergeTarget, Transitions.TransitionFinishCallback finishCallback)338     void mergeAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t,
339             IBinder mergeTarget, Transitions.TransitionFinishCallback finishCallback) {
340         if (mergeTarget != mAnimatingTransition) return;
341 
342         if (isPendingEnter(transition) && isPendingRecent(mergeTarget)) {
343             // Since there's an entering transition merged, recent transition no longer
344             // need to handle entering split screen after the transition finished.
345             mPendingRecent.setFinishedCallback(null);
346         }
347 
348         if (mActiveRemoteHandler != null) {
349             mActiveRemoteHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
350         } else {
351             for (int i = mAnimations.size() - 1; i >= 0; --i) {
352                 final Animator anim = mAnimations.get(i);
353                 mTransitions.getAnimExecutor().execute(anim::end);
354             }
355         }
356     }
357 
end()358     boolean end() {
359         // If It's remote, there's nothing we can do right now.
360         if (mActiveRemoteHandler != null) return false;
361         for (int i = mAnimations.size() - 1; i >= 0; --i) {
362             final Animator anim = mAnimations.get(i);
363             mTransitions.getAnimExecutor().execute(anim::end);
364         }
365         return true;
366     }
367 
onTransitionConsumed(@onNull IBinder transition, boolean aborted, @Nullable SurfaceControl.Transaction finishT)368     void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
369             @Nullable SurfaceControl.Transaction finishT) {
370         if (isPendingEnter(transition)) {
371             if (!aborted) {
372                 // An entering transition got merged, appends the rest operations to finish entering
373                 // split screen.
374                 mStageCoordinator.finishEnterSplitScreen(finishT);
375                 mPendingRemoteHandler = null;
376             }
377 
378             mPendingEnter.onConsumed(aborted);
379             mPendingEnter = null;
380             mPendingRemoteHandler = null;
381         } else if (isPendingDismiss(transition)) {
382             mPendingDismiss.onConsumed(aborted);
383             mPendingDismiss = null;
384         } else if (isPendingRecent(transition)) {
385             mPendingRecent.onConsumed(aborted);
386             mPendingRecent = null;
387             mPendingRemoteHandler = null;
388         } else if (isPendingResize(transition)) {
389             mPendingResize.onConsumed(aborted);
390             mPendingResize = null;
391         }
392     }
393 
onFinish(WindowContainerTransaction wct, WindowContainerTransactionCallback wctCB)394     void onFinish(WindowContainerTransaction wct, WindowContainerTransactionCallback wctCB) {
395         if (!mAnimations.isEmpty()) return;
396 
397         if (wct == null) wct = new WindowContainerTransaction();
398         if (isPendingEnter(mAnimatingTransition)) {
399             mPendingEnter.onFinished(wct, mFinishTransaction);
400             mPendingEnter = null;
401         } else if (isPendingRecent(mAnimatingTransition)) {
402             mPendingRecent.onFinished(wct, mFinishTransaction);
403             mPendingRecent = null;
404         } else if (isPendingDismiss(mAnimatingTransition)) {
405             mPendingDismiss.onFinished(wct, mFinishTransaction);
406             mPendingDismiss = null;
407         } else if (isPendingResize(mAnimatingTransition)) {
408             mPendingResize.onFinished(wct, mFinishTransaction);
409             mPendingResize = null;
410         }
411 
412         mPendingRemoteHandler = null;
413         mActiveRemoteHandler = null;
414         mAnimatingTransition = null;
415 
416         mOnFinish.run();
417         if (mFinishCallback != null) {
418             mFinishCallback.onTransitionFinished(wct /* wct */, wctCB /* wctCB */);
419             mFinishCallback = null;
420         }
421     }
422 
423     // TODO(shell-transitions): real animations
startExampleAnimation(@onNull SurfaceControl leash, boolean show)424     private void startExampleAnimation(@NonNull SurfaceControl leash, boolean show) {
425         final float end = show ? 1.f : 0.f;
426         final float start = 1.f - end;
427         final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
428         final ValueAnimator va = ValueAnimator.ofFloat(start, end);
429         va.setDuration(500);
430         va.addUpdateListener(animation -> {
431             float fraction = animation.getAnimatedFraction();
432             transaction.setAlpha(leash, start * (1.f - fraction) + end * fraction);
433             transaction.apply();
434         });
435         final Runnable finisher = () -> {
436             transaction.setAlpha(leash, end);
437             transaction.apply();
438             mTransactionPool.release(transaction);
439             mTransitions.getMainExecutor().execute(() -> {
440                 mAnimations.remove(va);
441                 onFinish(null /* wct */, null /* wctCB */);
442             });
443         };
444         va.addListener(new AnimatorListenerAdapter() {
445             @Override
446             public void onAnimationEnd(Animator animation) {
447                 finisher.run();
448             }
449 
450             @Override
451             public void onAnimationCancel(Animator animation) {
452                 finisher.run();
453             }
454         });
455         mAnimations.add(va);
456         mTransitions.getAnimExecutor().execute(va::start);
457     }
458 
459     // TODO(shell-transitions): real animations
startExampleResizeAnimation(@onNull SurfaceControl leash, @NonNull Rect startBounds, @NonNull Rect endBounds)460     private void startExampleResizeAnimation(@NonNull SurfaceControl leash,
461             @NonNull Rect startBounds, @NonNull Rect endBounds) {
462         final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
463         final ValueAnimator va = ValueAnimator.ofFloat(0.f, 1.f);
464         va.setDuration(500);
465         va.addUpdateListener(animation -> {
466             float fraction = animation.getAnimatedFraction();
467             transaction.setWindowCrop(leash,
468                     (int) (startBounds.width() * (1.f - fraction) + endBounds.width() * fraction),
469                     (int) (startBounds.height() * (1.f - fraction)
470                             + endBounds.height() * fraction));
471             transaction.setPosition(leash,
472                     startBounds.left * (1.f - fraction) + endBounds.left * fraction,
473                     startBounds.top * (1.f - fraction) + endBounds.top * fraction);
474             transaction.apply();
475         });
476         final Runnable finisher = () -> {
477             transaction.setWindowCrop(leash, 0, 0);
478             transaction.setPosition(leash, endBounds.left, endBounds.top);
479             transaction.apply();
480             mTransactionPool.release(transaction);
481             mTransitions.getMainExecutor().execute(() -> {
482                 mAnimations.remove(va);
483                 onFinish(null /* wct */, null /* wctCB */);
484             });
485         };
486         va.addListener(new AnimatorListenerAdapter() {
487             @Override
488             public void onAnimationEnd(Animator animation) {
489                 finisher.run();
490             }
491 
492             @Override
493             public void onAnimationCancel(Animator animation) {
494                 finisher.run();
495             }
496         });
497         mAnimations.add(va);
498         mTransitions.getAnimExecutor().execute(va::start);
499     }
500 
isOpeningTransition(TransitionInfo info)501     private boolean isOpeningTransition(TransitionInfo info) {
502         return Transitions.isOpeningType(info.getType())
503                 || info.getType() == TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE
504                 || info.getType() == TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
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     static class TransitSession {
519         final IBinder mTransition;
520         TransitionConsumedCallback mConsumedCallback;
521         TransitionFinishedCallback mFinishedCallback;
522 
523         /** Whether the transition was canceled. */
524         boolean mCanceled;
525 
TransitSession(IBinder transition, @Nullable TransitionConsumedCallback consumedCallback, @Nullable TransitionFinishedCallback finishedCallback)526         TransitSession(IBinder transition,
527                 @Nullable TransitionConsumedCallback consumedCallback,
528                 @Nullable TransitionFinishedCallback finishedCallback) {
529             mTransition = transition;
530             mConsumedCallback = consumedCallback;
531             mFinishedCallback = finishedCallback;
532 
533         }
534 
535         /** Sets transition consumed callback. */
setConsumedCallback(@ullable TransitionConsumedCallback callback)536         void setConsumedCallback(@Nullable TransitionConsumedCallback callback) {
537             mConsumedCallback = callback;
538         }
539 
540         /** Sets transition finished callback. */
setFinishedCallback(@ullable TransitionFinishedCallback callback)541         void setFinishedCallback(@Nullable TransitionFinishedCallback callback) {
542             mFinishedCallback = callback;
543         }
544 
545         /**
546          * Cancels the transition. This should be called before playing animation. A canceled
547          * transition will skip playing animation.
548          *
549          * @param finishedCb new finish callback to override.
550          */
cancel(@ullable TransitionFinishedCallback finishedCb)551         void cancel(@Nullable TransitionFinishedCallback finishedCb) {
552             mCanceled = true;
553             setFinishedCallback(finishedCb);
554         }
555 
onConsumed(boolean aborted)556         void onConsumed(boolean aborted) {
557             if (mConsumedCallback != null) {
558                 mConsumedCallback.onConsumed(aborted);
559             }
560         }
561 
onFinished(WindowContainerTransaction finishWct, SurfaceControl.Transaction finishT)562         void onFinished(WindowContainerTransaction finishWct,
563                 SurfaceControl.Transaction finishT) {
564             if (mFinishedCallback != null) {
565                 mFinishedCallback.onFinished(finishWct, finishT);
566             }
567         }
568     }
569 
570     /** Bundled information of dismiss transition. */
571     static class DismissTransition extends TransitSession {
572         final int mReason;
573         final @SplitScreen.StageType int mDismissTop;
574 
DismissTransition(IBinder transition, int reason, int dismissTop)575         DismissTransition(IBinder transition, int reason, int dismissTop) {
576             super(transition, null /* consumedCallback */, null /* finishedCallback */);
577             this.mReason = reason;
578             this.mDismissTop = dismissTop;
579         }
580     }
581 }
582