1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.wm.shell.transition;
18 
19 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
20 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
21 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
22 import static android.view.WindowManager.TRANSIT_CHANGE;
23 import static android.view.WindowManager.TRANSIT_TO_BACK;
24 import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
25 
26 import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
27 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
28 import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA;
29 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
30 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP;
31 import static com.android.wm.shell.util.TransitionUtil.isOpeningType;
32 
33 import android.annotation.NonNull;
34 import android.annotation.Nullable;
35 import android.app.PendingIntent;
36 import android.os.IBinder;
37 import android.util.Pair;
38 import android.view.SurfaceControl;
39 import android.view.WindowManager;
40 import android.window.TransitionInfo;
41 import android.window.TransitionRequestInfo;
42 import android.window.WindowContainerTransaction;
43 
44 import com.android.internal.protolog.common.ProtoLog;
45 import com.android.wm.shell.common.split.SplitScreenUtils;
46 import com.android.wm.shell.desktopmode.DesktopModeController;
47 import com.android.wm.shell.desktopmode.DesktopModeStatus;
48 import com.android.wm.shell.desktopmode.DesktopTasksController;
49 import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
50 import com.android.wm.shell.pip.PipTransitionController;
51 import com.android.wm.shell.protolog.ShellProtoLogGroup;
52 import com.android.wm.shell.recents.RecentsTransitionHandler;
53 import com.android.wm.shell.splitscreen.SplitScreen;
54 import com.android.wm.shell.splitscreen.SplitScreenController;
55 import com.android.wm.shell.splitscreen.StageCoordinator;
56 import com.android.wm.shell.sysui.ShellInit;
57 import com.android.wm.shell.unfold.UnfoldTransitionHandler;
58 import com.android.wm.shell.util.TransitionUtil;
59 
60 import java.util.ArrayList;
61 import java.util.Optional;
62 
63 /**
64  * A handler for dealing with transitions involving multiple other handlers. For example: an
65  * activity in split-screen going into PiP.
66  */
67 public class DefaultMixedHandler implements Transitions.TransitionHandler,
68         RecentsTransitionHandler.RecentsMixedHandler {
69 
70     private final Transitions mPlayer;
71     private PipTransitionController mPipHandler;
72     private RecentsTransitionHandler mRecentsHandler;
73     private StageCoordinator mSplitHandler;
74     private final KeyguardTransitionHandler mKeyguardHandler;
75     private DesktopModeController mDesktopModeController;
76     private DesktopTasksController mDesktopTasksController;
77     private UnfoldTransitionHandler mUnfoldHandler;
78 
79     private static class MixedTransition {
80         static final int TYPE_ENTER_PIP_FROM_SPLIT = 1;
81 
82         /** Both the display and split-state (enter/exit) is changing */
83         static final int TYPE_DISPLAY_AND_SPLIT_CHANGE = 2;
84 
85         /** Pip was entered while handling an intent with its own remoteTransition. */
86         static final int TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE = 3;
87 
88         /** Recents transition while split-screen foreground. */
89         static final int TYPE_RECENTS_DURING_SPLIT = 4;
90 
91         /** Keyguard exit/occlude/unocclude transition. */
92         static final int TYPE_KEYGUARD = 5;
93 
94         /** Recents Transition while in desktop mode. */
95         static final int TYPE_RECENTS_DURING_DESKTOP = 6;
96 
97         /** Fuld/Unfold transition. */
98         static final int TYPE_UNFOLD = 7;
99 
100         /** The default animation for this mixed transition. */
101         static final int ANIM_TYPE_DEFAULT = 0;
102 
103         /** For ENTER_PIP_FROM_SPLIT, indicates that this is a to-home animation. */
104         static final int ANIM_TYPE_GOING_HOME = 1;
105 
106         /** For RECENTS_DURING_SPLIT, is set when this turns into a pair->pair task switch. */
107         static final int ANIM_TYPE_PAIR_TO_PAIR = 1;
108 
109         final int mType;
110         int mAnimType = ANIM_TYPE_DEFAULT;
111         final IBinder mTransition;
112 
113         Transitions.TransitionHandler mLeftoversHandler = null;
114         WindowContainerTransaction mFinishWCT = null;
115 
116         /**
117          * Whether the transition has request for remote transition while mLeftoversHandler
118          * isn't remote transition handler.
119          * If true and the mLeftoversHandler can handle the transition, need to notify remote
120          * transition handler to consume the transition.
121          */
122         boolean mHasRequestToRemote;
123 
124         /**
125          * Mixed transitions are made up of multiple "parts". This keeps track of how many
126          * parts are currently animating.
127          */
128         int mInFlightSubAnimations = 0;
129 
MixedTransition(int type, IBinder transition)130         MixedTransition(int type, IBinder transition) {
131             mType = type;
132             mTransition = transition;
133         }
134 
joinFinishArgs(WindowContainerTransaction wct)135         void joinFinishArgs(WindowContainerTransaction wct) {
136             if (wct != null) {
137                 if (mFinishWCT == null) {
138                     mFinishWCT = wct;
139                 } else {
140                     mFinishWCT.merge(wct, true /* transfer */);
141                 }
142             }
143         }
144     }
145 
146     private final ArrayList<MixedTransition> mActiveTransitions = new ArrayList<>();
147 
DefaultMixedHandler(@onNull ShellInit shellInit, @NonNull Transitions player, Optional<SplitScreenController> splitScreenControllerOptional, @Nullable PipTransitionController pipTransitionController, Optional<RecentsTransitionHandler> recentsHandlerOptional, KeyguardTransitionHandler keyguardHandler, Optional<DesktopModeController> desktopModeControllerOptional, Optional<DesktopTasksController> desktopTasksControllerOptional, Optional<UnfoldTransitionHandler> unfoldHandler)148     public DefaultMixedHandler(@NonNull ShellInit shellInit, @NonNull Transitions player,
149             Optional<SplitScreenController> splitScreenControllerOptional,
150             @Nullable PipTransitionController pipTransitionController,
151             Optional<RecentsTransitionHandler> recentsHandlerOptional,
152             KeyguardTransitionHandler keyguardHandler,
153             Optional<DesktopModeController> desktopModeControllerOptional,
154             Optional<DesktopTasksController> desktopTasksControllerOptional,
155             Optional<UnfoldTransitionHandler> unfoldHandler) {
156         mPlayer = player;
157         mKeyguardHandler = keyguardHandler;
158         if (Transitions.ENABLE_SHELL_TRANSITIONS
159                 && pipTransitionController != null
160                 && splitScreenControllerOptional.isPresent()) {
161             // Add after dependencies because it is higher priority
162             shellInit.addInitCallback(() -> {
163                 mPipHandler = pipTransitionController;
164                 mSplitHandler = splitScreenControllerOptional.get().getTransitionHandler();
165                 mPlayer.addHandler(this);
166                 if (mSplitHandler != null) {
167                     mSplitHandler.setMixedHandler(this);
168                 }
169                 mRecentsHandler = recentsHandlerOptional.orElse(null);
170                 if (mRecentsHandler != null) {
171                     mRecentsHandler.addMixer(this);
172                 }
173                 mDesktopModeController = desktopModeControllerOptional.orElse(null);
174                 mDesktopTasksController = desktopTasksControllerOptional.orElse(null);
175                 mUnfoldHandler = unfoldHandler.orElse(null);
176             }, this);
177         }
178     }
179 
180     @Nullable
181     @Override
handleRequest(@onNull IBinder transition, @NonNull TransitionRequestInfo request)182     public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
183             @NonNull TransitionRequestInfo request) {
184         if (mSplitHandler.requestImpliesSplitToPip(request)) {
185             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a PiP-enter request while "
186                     + "Split-Screen is active, so treat it as Mixed.");
187             if (request.getRemoteTransition() != null) {
188                 throw new IllegalStateException("Unexpected remote transition in"
189                         + "pip-enter-from-split request");
190             }
191             mActiveTransitions.add(new MixedTransition(MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT,
192                     transition));
193 
194             WindowContainerTransaction out = new WindowContainerTransaction();
195             mPipHandler.augmentRequest(transition, request, out);
196             mSplitHandler.addEnterOrExitIfNeeded(request, out);
197             return out;
198         } else if (request.getRemoteTransition() != null
199                 && TransitionUtil.isOpeningType(request.getType())
200                 && (request.getTriggerTask() == null
201                 || (request.getTriggerTask().topActivityType != ACTIVITY_TYPE_HOME
202                         && request.getTriggerTask().topActivityType != ACTIVITY_TYPE_RECENTS))) {
203             // Only select transitions with an intent-provided remote-animation because that will
204             // usually grab priority and often won't handle PiP. If there isn't an intent-provided
205             // remote, then the transition will be dispatched normally and the PipHandler will
206             // pick it up.
207             Pair<Transitions.TransitionHandler, WindowContainerTransaction> handler =
208                     mPlayer.dispatchRequest(transition, request, this);
209             if (handler == null) {
210                 return null;
211             }
212             final MixedTransition mixed = new MixedTransition(
213                     MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE, transition);
214             mixed.mLeftoversHandler = handler.first;
215             mActiveTransitions.add(mixed);
216             if (mixed.mLeftoversHandler != mPlayer.getRemoteTransitionHandler()) {
217                 mixed.mHasRequestToRemote = true;
218                 mPlayer.getRemoteTransitionHandler().handleRequest(transition, request);
219             }
220             return handler.second;
221         } else if (mSplitHandler.isSplitScreenVisible()
222                 && isOpeningType(request.getType())
223                 && request.getTriggerTask() != null
224                 && request.getTriggerTask().getWindowingMode() == WINDOWING_MODE_FULLSCREEN
225                 && request.getTriggerTask().getActivityType() == ACTIVITY_TYPE_HOME) {
226             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a going-home request while "
227                     + "Split-Screen is foreground, so treat it as Mixed.");
228             Pair<Transitions.TransitionHandler, WindowContainerTransaction> handler =
229                     mPlayer.dispatchRequest(transition, request, this);
230             if (handler == null) {
231                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
232                         " Lean on the remote transition handler to fetch a proper remote via"
233                                 + " TransitionFilter");
234                 handler = new Pair<>(
235                         mPlayer.getRemoteTransitionHandler(),
236                         new WindowContainerTransaction());
237             }
238             final MixedTransition mixed = new MixedTransition(
239                     MixedTransition.TYPE_RECENTS_DURING_SPLIT, transition);
240             mixed.mLeftoversHandler = handler.first;
241             mActiveTransitions.add(mixed);
242             return handler.second;
243         } else if (mUnfoldHandler != null && mUnfoldHandler.hasUnfold(request)) {
244             final WindowContainerTransaction wct =
245                     mUnfoldHandler.handleRequest(transition, request);
246             if (wct != null) {
247                 final MixedTransition mixed = new MixedTransition(
248                         MixedTransition.TYPE_UNFOLD, transition);
249                 mixed.mLeftoversHandler = mUnfoldHandler;
250                 mActiveTransitions.add(mixed);
251             }
252             return wct;
253         }
254         return null;
255     }
256 
257     @Override
handleRecentsRequest(WindowContainerTransaction outWCT)258     public Transitions.TransitionHandler handleRecentsRequest(WindowContainerTransaction outWCT) {
259         if (mRecentsHandler != null && (mSplitHandler.isSplitScreenVisible()
260                 || DesktopModeStatus.isActive(mPlayer.getContext()))) {
261             return this;
262         }
263         return null;
264     }
265 
266     @Override
setRecentsTransition(IBinder transition)267     public void setRecentsTransition(IBinder transition) {
268         if (mSplitHandler.isSplitScreenVisible()) {
269             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while "
270                     + "Split-Screen is foreground, so treat it as Mixed.");
271             final MixedTransition mixed = new MixedTransition(
272                     MixedTransition.TYPE_RECENTS_DURING_SPLIT, transition);
273             mixed.mLeftoversHandler = mRecentsHandler;
274             mActiveTransitions.add(mixed);
275         } else if (DesktopModeStatus.isActive(mPlayer.getContext())) {
276             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while "
277                     + "desktop mode is active, so treat it as Mixed.");
278             final MixedTransition mixed = new MixedTransition(
279                     MixedTransition.TYPE_RECENTS_DURING_DESKTOP, transition);
280             mixed.mLeftoversHandler = mRecentsHandler;
281             mActiveTransitions.add(mixed);
282         } else {
283             throw new IllegalStateException("Accepted a recents transition but don't know how to"
284                     + " handle it");
285         }
286     }
287 
subCopy(@onNull TransitionInfo info, @WindowManager.TransitionType int newType, boolean withChanges)288     private TransitionInfo subCopy(@NonNull TransitionInfo info,
289             @WindowManager.TransitionType int newType, boolean withChanges) {
290         final TransitionInfo out = new TransitionInfo(newType, withChanges ? info.getFlags() : 0);
291         out.setTrack(info.getTrack());
292         out.setDebugId(info.getDebugId());
293         if (withChanges) {
294             for (int i = 0; i < info.getChanges().size(); ++i) {
295                 out.getChanges().add(info.getChanges().get(i));
296             }
297         }
298         for (int i = 0; i < info.getRootCount(); ++i) {
299             out.addRoot(info.getRoot(i));
300         }
301         out.setAnimationOptions(info.getAnimationOptions());
302         return out;
303     }
304 
isHomeOpening(@onNull TransitionInfo.Change change)305     private boolean isHomeOpening(@NonNull TransitionInfo.Change change) {
306         return change.getTaskInfo() != null
307                 && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME;
308     }
309 
isWallpaper(@onNull TransitionInfo.Change change)310     private boolean isWallpaper(@NonNull TransitionInfo.Change change) {
311         return (change.getFlags() & FLAG_IS_WALLPAPER) != 0;
312     }
313 
314     @Override
startAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback)315     public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
316             @NonNull SurfaceControl.Transaction startTransaction,
317             @NonNull SurfaceControl.Transaction finishTransaction,
318             @NonNull Transitions.TransitionFinishCallback finishCallback) {
319 
320         MixedTransition mixed = null;
321         for (int i = mActiveTransitions.size() - 1; i >= 0; --i) {
322             if (mActiveTransitions.get(i).mTransition != transition) continue;
323             mixed = mActiveTransitions.get(i);
324             break;
325         }
326 
327         // Offer Keyguard the opportunity to take over lock transitions - ideally we could know by
328         // the time of handleRequest, but we need more information than is available at that time.
329         if (KeyguardTransitionHandler.handles(info)) {
330             if (mixed != null && mixed.mType != MixedTransition.TYPE_KEYGUARD) {
331                 final MixedTransition keyguardMixed =
332                         new MixedTransition(MixedTransition.TYPE_KEYGUARD, transition);
333                 mActiveTransitions.add(keyguardMixed);
334                 final boolean hasAnimateKeyguard = animateKeyguard(keyguardMixed, info,
335                         startTransaction, finishTransaction, finishCallback);
336                 if (hasAnimateKeyguard) {
337                     ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
338                             "Converting mixed transition into a keyguard transition");
339                     // Consume the original mixed transition
340                     onTransitionConsumed(transition, false, null);
341                     return true;
342                 } else {
343                     // Keyguard handler cannot handle it, process through original mixed
344                     mActiveTransitions.remove(keyguardMixed);
345                 }
346             } else if (mPipHandler != null) {
347                 mPipHandler.syncPipSurfaceState(info, startTransaction, finishTransaction);
348             }
349         }
350 
351         if (mixed == null) return false;
352 
353         if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) {
354             return animateEnterPipFromSplit(mixed, info, startTransaction, finishTransaction,
355                     finishCallback);
356         } else if (mixed.mType == MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE) {
357             return false;
358         } else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
359             final boolean handledToPip = animateOpenIntentWithRemoteAndPip(mixed, info,
360                     startTransaction, finishTransaction, finishCallback);
361             // Consume the transition on remote handler if the leftover handler already handle this
362             // transition. And if it cannot, the transition will be handled by remote handler, so
363             // don't consume here.
364             // Need to check leftOverHandler as it may change in #animateOpenIntentWithRemoteAndPip
365             if (handledToPip && mixed.mHasRequestToRemote
366                     && mixed.mLeftoversHandler != mPlayer.getRemoteTransitionHandler()) {
367                 mPlayer.getRemoteTransitionHandler().onTransitionConsumed(transition, false, null);
368             }
369             return handledToPip;
370         } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) {
371             for (int i = info.getChanges().size() - 1; i >= 0; --i) {
372                 final TransitionInfo.Change change = info.getChanges().get(i);
373                 // Pip auto-entering info might be appended to recent transition like pressing
374                 // home-key in 3-button navigation. This offers split handler the opportunity to
375                 // handle split to pip animation.
376                 if (mPipHandler.isEnteringPip(change, info.getType())
377                         && mSplitHandler.getSplitItemPosition(change.getLastParent())
378                         != SPLIT_POSITION_UNDEFINED) {
379                     return animateEnterPipFromSplit(mixed, info, startTransaction,
380                             finishTransaction, finishCallback);
381                 }
382             }
383 
384             return animateRecentsDuringSplit(mixed, info, startTransaction, finishTransaction,
385                     finishCallback);
386         } else if (mixed.mType == MixedTransition.TYPE_KEYGUARD) {
387             return animateKeyguard(mixed, info, startTransaction, finishTransaction,
388                     finishCallback);
389         } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_DESKTOP) {
390             return animateRecentsDuringDesktop(mixed, info, startTransaction, finishTransaction,
391                     finishCallback);
392         } else if (mixed.mType == MixedTransition.TYPE_UNFOLD) {
393             return animateUnfold(mixed, info, startTransaction, finishTransaction, finishCallback);
394         } else {
395             mActiveTransitions.remove(mixed);
396             throw new IllegalStateException("Starting mixed animation without a known mixed type? "
397                     + mixed.mType);
398         }
399     }
400 
animateOpenIntentWithRemoteAndPip(@onNull MixedTransition mixed, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback)401     private boolean animateOpenIntentWithRemoteAndPip(@NonNull MixedTransition mixed,
402             @NonNull TransitionInfo info,
403             @NonNull SurfaceControl.Transaction startTransaction,
404             @NonNull SurfaceControl.Transaction finishTransaction,
405             @NonNull Transitions.TransitionFinishCallback finishCallback) {
406         TransitionInfo.Change pipChange = null;
407         for (int i = info.getChanges().size() - 1; i >= 0; --i) {
408             TransitionInfo.Change change = info.getChanges().get(i);
409             if (mPipHandler.isEnteringPip(change, info.getType())) {
410                 if (pipChange != null) {
411                     throw new IllegalStateException("More than 1 pip-entering changes in one"
412                             + " transition? " + info);
413                 }
414                 pipChange = change;
415                 info.getChanges().remove(i);
416             }
417         }
418         Transitions.TransitionFinishCallback finishCB = (wct) -> {
419             --mixed.mInFlightSubAnimations;
420             mixed.joinFinishArgs(wct);
421             if (mixed.mInFlightSubAnimations > 0) return;
422             mActiveTransitions.remove(mixed);
423             finishCallback.onTransitionFinished(mixed.mFinishWCT);
424         };
425         if (pipChange == null) {
426             if (mixed.mLeftoversHandler != null) {
427                 mixed.mInFlightSubAnimations = 1;
428                 if (mixed.mLeftoversHandler.startAnimation(mixed.mTransition,
429                         info, startTransaction, finishTransaction, finishCB)) {
430                     return true;
431                 }
432             }
433             mActiveTransitions.remove(mixed);
434             return false;
435         }
436         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Splitting PIP into a separate"
437                         + " animation because remote-animation likely doesn't support it");
438         // Split the transition into 2 parts: the pip part and the rest.
439         mixed.mInFlightSubAnimations = 2;
440         // make a new startTransaction because pip's startEnterAnimation "consumes" it so
441         // we need a separate one to send over to launcher.
442         SurfaceControl.Transaction otherStartT = new SurfaceControl.Transaction();
443 
444         mPipHandler.startEnterAnimation(pipChange, otherStartT, finishTransaction, finishCB);
445 
446         // Dispatch the rest of the transition normally.
447         if (mixed.mLeftoversHandler != null
448                 && mixed.mLeftoversHandler.startAnimation(mixed.mTransition, info,
449                     startTransaction, finishTransaction, finishCB)) {
450             return true;
451         }
452         mixed.mLeftoversHandler = mPlayer.dispatchTransition(mixed.mTransition, info,
453                 startTransaction, finishTransaction, finishCB, this);
454         return true;
455     }
456 
animateEnterPipFromSplit(@onNull final MixedTransition mixed, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback)457     private boolean animateEnterPipFromSplit(@NonNull final MixedTransition mixed,
458             @NonNull TransitionInfo info,
459             @NonNull SurfaceControl.Transaction startTransaction,
460             @NonNull SurfaceControl.Transaction finishTransaction,
461             @NonNull Transitions.TransitionFinishCallback finishCallback) {
462         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for "
463                 + "entering PIP while Split-Screen is foreground.");
464         TransitionInfo.Change pipChange = null;
465         TransitionInfo.Change wallpaper = null;
466         final TransitionInfo everythingElse = subCopy(info, TRANSIT_TO_BACK, true /* changes */);
467         boolean homeIsOpening = false;
468         for (int i = info.getChanges().size() - 1; i >= 0; --i) {
469             TransitionInfo.Change change = info.getChanges().get(i);
470             if (mPipHandler.isEnteringPip(change, info.getType())) {
471                 if (pipChange != null) {
472                     throw new IllegalStateException("More than 1 pip-entering changes in one"
473                             + " transition? " + info);
474                 }
475                 pipChange = change;
476                 // going backwards, so remove-by-index is fine.
477                 everythingElse.getChanges().remove(i);
478             } else if (isHomeOpening(change)) {
479                 homeIsOpening = true;
480             } else if (isWallpaper(change)) {
481                 wallpaper = change;
482             }
483         }
484         if (pipChange == null) {
485             // um, something probably went wrong.
486             mActiveTransitions.remove(mixed);
487             return false;
488         }
489         final boolean isGoingHome = homeIsOpening;
490         Transitions.TransitionFinishCallback finishCB = (wct) -> {
491             --mixed.mInFlightSubAnimations;
492             mixed.joinFinishArgs(wct);
493             if (mixed.mInFlightSubAnimations > 0) return;
494             mActiveTransitions.remove(mixed);
495             if (isGoingHome) {
496                 mSplitHandler.onTransitionAnimationComplete();
497             }
498             finishCallback.onTransitionFinished(mixed.mFinishWCT);
499         };
500         if (isGoingHome || mSplitHandler.getSplitItemPosition(pipChange.getLastParent())
501                 != SPLIT_POSITION_UNDEFINED) {
502             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animation is actually mixed "
503                     + "since entering-PiP caused us to leave split and return home.");
504             // We need to split the transition into 2 parts: the pip part (animated by pip)
505             // and the dismiss-part (animated by launcher).
506             mixed.mInFlightSubAnimations = 2;
507             // immediately make the wallpaper visible (so that we don't see it pop-in during
508             // the time it takes to start recents animation (which is remote).
509             if (wallpaper != null) {
510                 startTransaction.show(wallpaper.getLeash()).setAlpha(wallpaper.getLeash(), 1.f);
511             }
512             // make a new startTransaction because pip's startEnterAnimation "consumes" it so
513             // we need a separate one to send over to launcher.
514             SurfaceControl.Transaction otherStartT = new SurfaceControl.Transaction();
515             @SplitScreen.StageType int topStageToKeep = STAGE_TYPE_UNDEFINED;
516             if (mSplitHandler.isSplitScreenVisible()) {
517                 // The non-going home case, we could be pip-ing one of the split stages and keep
518                 // showing the other
519                 for (int i = info.getChanges().size() - 1; i >= 0; --i) {
520                     TransitionInfo.Change change = info.getChanges().get(i);
521                     if (change == pipChange) {
522                         // Ignore the change/task that's going into Pip
523                         continue;
524                     }
525                     @SplitScreen.StageType int splitItemStage =
526                             mSplitHandler.getSplitItemStage(change.getLastParent());
527                     if (splitItemStage != STAGE_TYPE_UNDEFINED) {
528                         topStageToKeep = splitItemStage;
529                         break;
530                     }
531                 }
532             }
533             // Let split update internal state for dismiss.
534             mSplitHandler.prepareDismissAnimation(topStageToKeep,
535                     EXIT_REASON_CHILD_TASK_ENTER_PIP, everythingElse, otherStartT,
536                     finishTransaction);
537 
538             // We are trying to accommodate launcher's close animation which can't handle the
539             // divider-bar, so if split-handler is closing the divider-bar, just hide it and remove
540             // from transition info.
541             for (int i = everythingElse.getChanges().size() - 1; i >= 0; --i) {
542                 if ((everythingElse.getChanges().get(i).getFlags() & FLAG_IS_DIVIDER_BAR) != 0) {
543                     everythingElse.getChanges().remove(i);
544                     break;
545                 }
546             }
547 
548             mPipHandler.setEnterAnimationType(ANIM_TYPE_ALPHA);
549             mPipHandler.startEnterAnimation(pipChange, startTransaction, finishTransaction,
550                     finishCB);
551             // Dispatch the rest of the transition normally. This will most-likely be taken by
552             // recents or default handler.
553             mixed.mLeftoversHandler = mPlayer.dispatchTransition(mixed.mTransition, everythingElse,
554                     otherStartT, finishTransaction, finishCB, this);
555         } else {
556             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "  Not leaving split, so just "
557                     + "forward animation to Pip-Handler.");
558             // This happens if the pip-ing activity is in a multi-activity task (and thus a
559             // new pip task is spawned). In this case, we don't actually exit split so we can
560             // just let pip transition handle the animation verbatim.
561             mixed.mInFlightSubAnimations = 1;
562             mPipHandler.startAnimation(mixed.mTransition, info, startTransaction, finishTransaction,
563                     finishCB);
564         }
565         return true;
566     }
567 
unlinkMissingParents(TransitionInfo from)568     private void unlinkMissingParents(TransitionInfo from) {
569         for (int i = 0; i < from.getChanges().size(); ++i) {
570             final TransitionInfo.Change chg = from.getChanges().get(i);
571             if (chg.getParent() == null) continue;
572             if (from.getChange(chg.getParent()) == null) {
573                 from.getChanges().get(i).setParent(null);
574             }
575         }
576     }
577 
isWithinTask(TransitionInfo info, TransitionInfo.Change chg)578     private boolean isWithinTask(TransitionInfo info, TransitionInfo.Change chg) {
579         TransitionInfo.Change curr = chg;
580         while (curr != null) {
581             if (curr.getTaskInfo() != null) return true;
582             if (curr.getParent() == null) break;
583             curr = info.getChange(curr.getParent());
584         }
585         return false;
586     }
587 
588     /**
589      * This is intended to be called by SplitCoordinator as a helper to mix a split handling
590      * transition with an entering-pip change. The use-case for this is when an auto-pip change
591      * gets collected into the transition which has already claimed by
592      * StageCoordinator.handleRequest. This happens when launching a fullscreen app while having an
593      * auto-pip activity in the foreground split pair.
594      */
595     // TODO(b/287704263): Remove when split/mixed are reversed.
animatePendingEnterPipFromSplit(IBinder transition, TransitionInfo info, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, Transitions.TransitionFinishCallback finishCallback)596     public boolean animatePendingEnterPipFromSplit(IBinder transition, TransitionInfo info,
597             SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
598             Transitions.TransitionFinishCallback finishCallback) {
599         final MixedTransition mixed = new MixedTransition(
600                 MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT, transition);
601         mActiveTransitions.add(mixed);
602         return animateEnterPipFromSplit(mixed, info, startT, finishT, finishCallback);
603     }
604 
605     /**
606      * This is intended to be called by SplitCoordinator as a helper to mix an already-pending
607      * split transition with a display-change. The use-case for this is when a display
608      * change/rotation gets collected into a split-screen enter/exit transition which has already
609      * been claimed by StageCoordinator.handleRequest. This happens during launcher tests.
610      */
animatePendingSplitWithDisplayChange(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startT, @NonNull SurfaceControl.Transaction finishT, @NonNull Transitions.TransitionFinishCallback finishCallback)611     public boolean animatePendingSplitWithDisplayChange(@NonNull IBinder transition,
612             @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startT,
613             @NonNull SurfaceControl.Transaction finishT,
614             @NonNull Transitions.TransitionFinishCallback finishCallback) {
615         final TransitionInfo everythingElse = subCopy(info, info.getType(), true /* withChanges */);
616         final TransitionInfo displayPart = subCopy(info, TRANSIT_CHANGE, false /* withChanges */);
617         for (int i = info.getChanges().size() - 1; i >= 0; --i) {
618             TransitionInfo.Change change = info.getChanges().get(i);
619             if (isWithinTask(info, change)) continue;
620             displayPart.addChange(change);
621             everythingElse.getChanges().remove(i);
622         }
623         if (displayPart.getChanges().isEmpty()) return false;
624         unlinkMissingParents(everythingElse);
625         final MixedTransition mixed = new MixedTransition(
626                 MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE, transition);
627         mActiveTransitions.add(mixed);
628         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animation is a mix of display change "
629                 + "and split change.");
630         // We need to split the transition into 2 parts: the split part and the display part.
631         mixed.mInFlightSubAnimations = 2;
632 
633         Transitions.TransitionFinishCallback finishCB = (wct) -> {
634             --mixed.mInFlightSubAnimations;
635             mixed.joinFinishArgs(wct);
636             if (mixed.mInFlightSubAnimations > 0) return;
637             mActiveTransitions.remove(mixed);
638             finishCallback.onTransitionFinished(mixed.mFinishWCT);
639         };
640 
641         // Dispatch the display change. This will most-likely be taken by the default handler.
642         // Do this first since the first handler used will apply the startT; the display change
643         // needs to take a screenshot before that happens so we need it to be the first handler.
644         mixed.mLeftoversHandler = mPlayer.dispatchTransition(mixed.mTransition, displayPart,
645                 startT, finishT, finishCB, mSplitHandler);
646 
647         // Note: at this point, startT has probably already been applied, so we are basically
648         // giving splitHandler an empty startT. This is currently OK because display-change will
649         // grab a screenshot and paste it on top anyways.
650         mSplitHandler.startPendingAnimation(transition, everythingElse, startT, finishT, finishCB);
651         return true;
652     }
653 
animateRecentsDuringSplit(@onNull final MixedTransition mixed, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback)654     private boolean animateRecentsDuringSplit(@NonNull final MixedTransition mixed,
655             @NonNull TransitionInfo info,
656             @NonNull SurfaceControl.Transaction startTransaction,
657             @NonNull SurfaceControl.Transaction finishTransaction,
658             @NonNull Transitions.TransitionFinishCallback finishCallback) {
659         // Split-screen is only interested in the recents transition finishing (and merging), so
660         // just wrap finish and start recents animation directly.
661         Transitions.TransitionFinishCallback finishCB = (wct) -> {
662             mixed.mInFlightSubAnimations = 0;
663             mActiveTransitions.remove(mixed);
664             // If pair-to-pair switching, the post-recents clean-up isn't needed.
665             wct = wct != null ? wct : new WindowContainerTransaction();
666             if (mixed.mAnimType != MixedTransition.ANIM_TYPE_PAIR_TO_PAIR) {
667                 mSplitHandler.onRecentsInSplitAnimationFinish(wct, finishTransaction);
668             } else {
669                 // notify pair-to-pair recents animation finish
670                 mSplitHandler.onRecentsPairToPairAnimationFinish(wct);
671             }
672             mSplitHandler.onTransitionAnimationComplete();
673             finishCallback.onTransitionFinished(wct);
674         };
675         mixed.mInFlightSubAnimations = 1;
676         mSplitHandler.onRecentsInSplitAnimationStart(info);
677         final boolean handled = mixed.mLeftoversHandler.startAnimation(mixed.mTransition, info,
678                 startTransaction, finishTransaction, finishCB);
679         if (!handled) {
680             mSplitHandler.onRecentsInSplitAnimationCanceled();
681             mActiveTransitions.remove(mixed);
682         }
683         return handled;
684     }
685 
animateKeyguard(@onNull final MixedTransition mixed, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback)686     private boolean animateKeyguard(@NonNull final MixedTransition mixed,
687             @NonNull TransitionInfo info,
688             @NonNull SurfaceControl.Transaction startTransaction,
689             @NonNull SurfaceControl.Transaction finishTransaction,
690             @NonNull Transitions.TransitionFinishCallback finishCallback) {
691         final Transitions.TransitionFinishCallback finishCB = (wct) -> {
692             mixed.mInFlightSubAnimations--;
693             if (mixed.mInFlightSubAnimations == 0) {
694                 mActiveTransitions.remove(mixed);
695                 finishCallback.onTransitionFinished(wct);
696             }
697         };
698         mixed.mInFlightSubAnimations++;
699         // Sync pip state.
700         if (mPipHandler != null) {
701             mPipHandler.syncPipSurfaceState(info, startTransaction, finishTransaction);
702         }
703         if (!mKeyguardHandler.startAnimation(
704                 mixed.mTransition, info, startTransaction, finishTransaction, finishCB)) {
705             mixed.mInFlightSubAnimations--;
706             return false;
707         }
708         return true;
709     }
710 
animateRecentsDuringDesktop(@onNull final MixedTransition mixed, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback)711     private boolean animateRecentsDuringDesktop(@NonNull final MixedTransition mixed,
712             @NonNull TransitionInfo info,
713             @NonNull SurfaceControl.Transaction startTransaction,
714             @NonNull SurfaceControl.Transaction finishTransaction,
715             @NonNull Transitions.TransitionFinishCallback finishCallback) {
716         boolean consumed = mRecentsHandler.startAnimation(
717                 mixed.mTransition, info, startTransaction, finishTransaction, finishCallback);
718         if (!consumed) {
719             return false;
720         }
721         //Sync desktop mode state (proto 1)
722         if (mDesktopModeController != null) {
723             mDesktopModeController.syncSurfaceState(info, finishTransaction);
724             return true;
725         }
726         //Sync desktop mode state (proto 2)
727         if (mDesktopTasksController != null) {
728             mDesktopTasksController.syncSurfaceState(info, finishTransaction);
729             return true;
730         }
731 
732         return false;
733     }
734 
animateUnfold(@onNull final MixedTransition mixed, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback)735     private boolean animateUnfold(@NonNull final MixedTransition mixed,
736             @NonNull TransitionInfo info,
737             @NonNull SurfaceControl.Transaction startTransaction,
738             @NonNull SurfaceControl.Transaction finishTransaction,
739             @NonNull Transitions.TransitionFinishCallback finishCallback) {
740         final Transitions.TransitionFinishCallback finishCB = (wct) -> {
741             mixed.mInFlightSubAnimations--;
742             if (mixed.mInFlightSubAnimations > 0) return;
743             mActiveTransitions.remove(mixed);
744             finishCallback.onTransitionFinished(wct);
745         };
746         mixed.mInFlightSubAnimations = 1;
747         // Sync pip state.
748         if (mPipHandler != null) {
749             mPipHandler.syncPipSurfaceState(info, startTransaction, finishTransaction);
750         }
751         if (mSplitHandler != null && mSplitHandler.isSplitActive()) {
752             mSplitHandler.updateSurfaces(startTransaction);
753         }
754         return mUnfoldHandler.startAnimation(
755                 mixed.mTransition, info, startTransaction, finishTransaction, finishCB);
756     }
757 
758     /** Use to when split use intent to enter, check if this enter transition should be mixed or
759      * not.*/
shouldSplitEnterMixed(PendingIntent intent)760     public boolean shouldSplitEnterMixed(PendingIntent intent) {
761         // Check if this intent package is same as pip one or not, if true we want let the pip
762         // task enter split.
763         if (mPipHandler != null) {
764             return mPipHandler.isInPipPackage(SplitScreenUtils.getPackageName(intent.getIntent()));
765         }
766         return false;
767     }
768 
769     /** @return whether the transition-request represents a pip-entry. */
requestHasPipEnter(TransitionRequestInfo request)770     public boolean requestHasPipEnter(TransitionRequestInfo request) {
771         return mPipHandler.requestHasPipEnter(request);
772     }
773 
774     /** Whether a particular change is a window that is entering pip. */
775     // TODO(b/287704263): Remove when split/mixed are reversed.
isEnteringPip(TransitionInfo.Change change, @WindowManager.TransitionType int transitType)776     public boolean isEnteringPip(TransitionInfo.Change change,
777             @WindowManager.TransitionType int transitType) {
778         return mPipHandler.isEnteringPip(change, transitType);
779     }
780 
781     @Override
mergeAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, @NonNull Transitions.TransitionFinishCallback finishCallback)782     public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
783             @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
784             @NonNull Transitions.TransitionFinishCallback finishCallback) {
785         for (int i = 0; i < mActiveTransitions.size(); ++i) {
786             if (mActiveTransitions.get(i).mTransition != mergeTarget) continue;
787             MixedTransition mixed = mActiveTransitions.get(i);
788             if (mixed.mInFlightSubAnimations <= 0) {
789                 // Already done, so no need to end it.
790                 return;
791             }
792             if (mixed.mType == MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE) {
793                 // queue since no actual animation.
794             } else if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) {
795                 if (mixed.mAnimType == MixedTransition.ANIM_TYPE_GOING_HOME) {
796                     boolean ended = mSplitHandler.end();
797                     // If split couldn't end (because it is remote), then don't end everything else
798                     // since we have to play out the animation anyways.
799                     if (!ended) return;
800                     mPipHandler.end();
801                     if (mixed.mLeftoversHandler != null) {
802                         mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget,
803                                 finishCallback);
804                     }
805                 } else {
806                     mPipHandler.end();
807                 }
808             } else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
809                 mPipHandler.end();
810                 if (mixed.mLeftoversHandler != null) {
811                     mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget,
812                             finishCallback);
813                 }
814             } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) {
815                 if (mSplitHandler.isPendingEnter(transition)) {
816                     // Recents -> enter-split means that we are switching from one pair to
817                     // another pair.
818                     mixed.mAnimType = MixedTransition.ANIM_TYPE_PAIR_TO_PAIR;
819                 }
820                 mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget,
821                         finishCallback);
822             } else if (mixed.mType == MixedTransition.TYPE_KEYGUARD) {
823                 mKeyguardHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
824             } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_DESKTOP) {
825                 mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget,
826                         finishCallback);
827             } else if (mixed.mType == MixedTransition.TYPE_UNFOLD) {
828                 mUnfoldHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
829             } else {
830                 throw new IllegalStateException("Playing a mixed transition with unknown type? "
831                         + mixed.mType);
832             }
833         }
834     }
835 
836     @Override
onTransitionConsumed(@onNull IBinder transition, boolean aborted, @Nullable SurfaceControl.Transaction finishT)837     public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
838             @Nullable SurfaceControl.Transaction finishT) {
839         MixedTransition mixed = null;
840         for (int i = mActiveTransitions.size() - 1; i >= 0; --i) {
841             if (mActiveTransitions.get(i).mTransition != transition) continue;
842             mixed = mActiveTransitions.remove(i);
843             break;
844         }
845         if (mixed == null) return;
846         if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) {
847             mPipHandler.onTransitionConsumed(transition, aborted, finishT);
848         } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) {
849             mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
850         } else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
851             mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
852         } else if (mixed.mType == MixedTransition.TYPE_KEYGUARD) {
853             mKeyguardHandler.onTransitionConsumed(transition, aborted, finishT);
854         } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_DESKTOP) {
855             mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
856         } else if (mixed.mType == MixedTransition.TYPE_UNFOLD) {
857             mUnfoldHandler.onTransitionConsumed(transition, aborted, finishT);
858         }
859         if (mixed.mHasRequestToRemote) {
860             mPlayer.getRemoteTransitionHandler().onTransitionConsumed(transition, aborted, finishT);
861         }
862     }
863 }
864