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