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