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.Display.DEFAULT_DISPLAY;
23 import static android.view.WindowManager.TRANSIT_CHANGE;
24 import static android.view.WindowManager.TRANSIT_PIP;
25 import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
26 
27 import static com.android.wm.shell.shared.TransitionUtil.isOpeningType;
28 
29 import android.annotation.NonNull;
30 import android.annotation.Nullable;
31 import android.app.PendingIntent;
32 import android.os.IBinder;
33 import android.util.ArrayMap;
34 import android.util.Pair;
35 import android.view.SurfaceControl;
36 import android.view.WindowManager;
37 import android.window.TransitionInfo;
38 import android.window.TransitionRequestInfo;
39 import android.window.WindowContainerToken;
40 import android.window.WindowContainerTransaction;
41 
42 import com.android.internal.protolog.common.ProtoLog;
43 import com.android.wm.shell.ShellTaskOrganizer;
44 import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
45 import com.android.wm.shell.common.split.SplitScreenUtils;
46 import com.android.wm.shell.desktopmode.DesktopTasksController;
47 import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
48 import com.android.wm.shell.pip.PipTransitionController;
49 import com.android.wm.shell.protolog.ShellProtoLogGroup;
50 import com.android.wm.shell.recents.RecentsTransitionHandler;
51 import com.android.wm.shell.shared.TransitionUtil;
52 import com.android.wm.shell.splitscreen.SplitScreenController;
53 import com.android.wm.shell.splitscreen.StageCoordinator;
54 import com.android.wm.shell.sysui.ShellInit;
55 import com.android.wm.shell.unfold.UnfoldTransitionHandler;
56 
57 import java.util.ArrayList;
58 import java.util.Map;
59 import java.util.Optional;
60 import java.util.function.Consumer;
61 
62 /**
63  * A handler for dealing with transitions involving multiple other handlers. For example: an
64  * activity in split-screen going into PiP. Note this is provided as a handset-specific
65  * implementation of {@code MixedTransitionHandler}.
66  */
67 public class DefaultMixedHandler implements MixedTransitionHandler,
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 DesktopTasksController mDesktopTasksController;
76     private UnfoldTransitionHandler mUnfoldHandler;
77     private ActivityEmbeddingController mActivityEmbeddingController;
78 
79     abstract static class MixedTransition {
80         /** Entering Pip from split, breaks split. */
81         static final int TYPE_ENTER_PIP_FROM_SPLIT = 1;
82 
83         /** Both the display and split-state (enter/exit) is changing */
84         static final int TYPE_DISPLAY_AND_SPLIT_CHANGE = 2;
85 
86         /** Pip was entered while handling an intent with its own remoteTransition. */
87         static final int TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE = 3;
88 
89         /** Recents transition while split-screen foreground. */
90         static final int TYPE_RECENTS_DURING_SPLIT = 4;
91 
92         /** Keyguard exit/occlude/unocclude transition. */
93         static final int TYPE_KEYGUARD = 5;
94 
95         /** Recents transition on top of the lock screen. */
96         static final int TYPE_RECENTS_DURING_KEYGUARD = 6;
97 
98         /** Recents Transition while in desktop mode. */
99         static final int TYPE_RECENTS_DURING_DESKTOP = 7;
100 
101         /** Fold/Unfold transition. */
102         static final int TYPE_UNFOLD = 8;
103 
104         /** Enter pip from one of the Activity Embedding windows. */
105         static final int TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING = 9;
106 
107         /** Entering Pip from split, but replace the Pip stage instead of breaking split. */
108         static final int TYPE_ENTER_PIP_REPLACE_FROM_SPLIT = 10;
109 
110         /** The display changes when pip is entering. */
111         static final int TYPE_ENTER_PIP_WITH_DISPLAY_CHANGE = 11;
112 
113         /** The default animation for this mixed transition. */
114         static final int ANIM_TYPE_DEFAULT = 0;
115 
116         /** For ENTER_PIP_FROM_SPLIT, indicates that this is a to-home animation. */
117         static final int ANIM_TYPE_GOING_HOME = 1;
118 
119         /** For RECENTS_DURING_SPLIT, is set when this turns into a pair->pair task switch. */
120         static final int ANIM_TYPE_PAIR_TO_PAIR = 1;
121 
122         final int mType;
123         int mAnimType = ANIM_TYPE_DEFAULT;
124         final IBinder mTransition;
125 
126         protected final Transitions mPlayer;
127         protected final MixedTransitionHandler mMixedHandler;
128         protected final PipTransitionController mPipHandler;
129         protected final StageCoordinator mSplitHandler;
130         protected final KeyguardTransitionHandler mKeyguardHandler;
131 
132         Transitions.TransitionHandler mLeftoversHandler = null;
133         TransitionInfo mInfo = null;
134         WindowContainerTransaction mFinishWCT = null;
135         SurfaceControl.Transaction mFinishT = null;
136         Transitions.TransitionFinishCallback mFinishCB = null;
137 
138         /**
139          * Whether the transition has request for remote transition while mLeftoversHandler
140          * isn't remote transition handler.
141          * If true and the mLeftoversHandler can handle the transition, need to notify remote
142          * transition handler to consume the transition.
143          */
144         boolean mHasRequestToRemote;
145 
146         /**
147          * Mixed transitions are made up of multiple "parts". This keeps track of how many
148          * parts are currently animating.
149          */
150         int mInFlightSubAnimations = 0;
151 
MixedTransition(int type, IBinder transition, Transitions player, MixedTransitionHandler mixedHandler, PipTransitionController pipHandler, StageCoordinator splitHandler, KeyguardTransitionHandler keyguardHandler)152         MixedTransition(int type, IBinder transition, Transitions player,
153                 MixedTransitionHandler mixedHandler, PipTransitionController pipHandler,
154                 StageCoordinator splitHandler, KeyguardTransitionHandler keyguardHandler) {
155             mType = type;
156             mTransition = transition;
157             mPlayer = player;
158             mMixedHandler = mixedHandler;
159             mPipHandler = pipHandler;
160             mSplitHandler = splitHandler;
161             mKeyguardHandler = keyguardHandler;
162         }
163 
startAnimation( @onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback)164         abstract boolean startAnimation(
165                 @NonNull IBinder transition, @NonNull TransitionInfo info,
166                 @NonNull SurfaceControl.Transaction startTransaction,
167                 @NonNull SurfaceControl.Transaction finishTransaction,
168                 @NonNull Transitions.TransitionFinishCallback finishCallback);
169 
mergeAnimation( @onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, @NonNull Transitions.TransitionFinishCallback finishCallback)170         abstract void mergeAnimation(
171                 @NonNull IBinder transition, @NonNull TransitionInfo info,
172                 @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
173                 @NonNull Transitions.TransitionFinishCallback finishCallback);
174 
onTransitionConsumed( @onNull IBinder transition, boolean aborted, @Nullable SurfaceControl.Transaction finishT)175         abstract void onTransitionConsumed(
176                 @NonNull IBinder transition, boolean aborted,
177                 @Nullable SurfaceControl.Transaction finishT);
178 
startSubAnimation( Transitions.TransitionHandler handler, TransitionInfo info, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT)179         protected boolean startSubAnimation(
180                 Transitions.TransitionHandler handler, TransitionInfo info,
181                 SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) {
182             if (mInfo != null) {
183                 ProtoLog.d(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
184                         "startSubAnimation #%d.%d", mInfo.getDebugId(), info.getDebugId());
185             }
186             mInFlightSubAnimations++;
187             if (!handler.startAnimation(
188                     mTransition, info, startT, finishT, wct -> onSubAnimationFinished(info, wct))) {
189                 mInFlightSubAnimations--;
190                 return false;
191             }
192             return true;
193         }
194 
onSubAnimationFinished(TransitionInfo info, WindowContainerTransaction wct)195         private void onSubAnimationFinished(TransitionInfo info, WindowContainerTransaction wct) {
196             mInFlightSubAnimations--;
197             if (mInfo != null) {
198                 ProtoLog.d(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
199                         "onSubAnimationFinished #%d.%d remaining=%d",
200                         mInfo.getDebugId(), info.getDebugId(), mInFlightSubAnimations);
201             }
202 
203             joinFinishArgs(wct);
204 
205             if (mInFlightSubAnimations == 0) {
206                 mFinishCB.onTransitionFinished(mFinishWCT);
207             }
208         }
209 
joinFinishArgs(WindowContainerTransaction wct)210         void joinFinishArgs(WindowContainerTransaction wct) {
211             if (wct != null) {
212                 if (mFinishWCT == null) {
213                     mFinishWCT = wct;
214                 } else {
215                     mFinishWCT.merge(wct, true /* transfer */);
216                 }
217             }
218         }
219     }
220 
221     private final ArrayList<MixedTransition> mActiveTransitions = new ArrayList<>();
222 
DefaultMixedHandler(@onNull ShellInit shellInit, @NonNull Transitions player, Optional<SplitScreenController> splitScreenControllerOptional, @Nullable PipTransitionController pipTransitionController, Optional<RecentsTransitionHandler> recentsHandlerOptional, KeyguardTransitionHandler keyguardHandler, Optional<DesktopTasksController> desktopTasksControllerOptional, Optional<UnfoldTransitionHandler> unfoldHandler, Optional<ActivityEmbeddingController> activityEmbeddingController)223     public DefaultMixedHandler(@NonNull ShellInit shellInit, @NonNull Transitions player,
224             Optional<SplitScreenController> splitScreenControllerOptional,
225             @Nullable PipTransitionController pipTransitionController,
226             Optional<RecentsTransitionHandler> recentsHandlerOptional,
227             KeyguardTransitionHandler keyguardHandler,
228             Optional<DesktopTasksController> desktopTasksControllerOptional,
229             Optional<UnfoldTransitionHandler> unfoldHandler,
230             Optional<ActivityEmbeddingController> activityEmbeddingController) {
231         mPlayer = player;
232         mKeyguardHandler = keyguardHandler;
233         if (Transitions.ENABLE_SHELL_TRANSITIONS
234                 && pipTransitionController != null
235                 && splitScreenControllerOptional.isPresent()) {
236             // Add after dependencies because it is higher priority
237             shellInit.addInitCallback(() -> {
238                 mPipHandler = pipTransitionController;
239                 pipTransitionController.setMixedHandler(this);
240                 mSplitHandler = splitScreenControllerOptional.get().getTransitionHandler();
241                 mPlayer.addHandler(this);
242                 if (mSplitHandler != null) {
243                     mSplitHandler.setMixedHandler(this);
244                 }
245                 mRecentsHandler = recentsHandlerOptional.orElse(null);
246                 if (mRecentsHandler != null) {
247                     mRecentsHandler.addMixer(this);
248                 }
249                 mDesktopTasksController = desktopTasksControllerOptional.orElse(null);
250                 mUnfoldHandler = unfoldHandler.orElse(null);
251                 mActivityEmbeddingController = activityEmbeddingController.orElse(null);
252             }, this);
253         }
254     }
255 
256     @Nullable
257     @Override
handleRequest(@onNull IBinder transition, @NonNull TransitionRequestInfo request)258     public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
259             @NonNull TransitionRequestInfo request) {
260         if (mSplitHandler.requestImpliesSplitToPip(request)) {
261             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a PiP-enter request while "
262                     + "Split-Screen is active, so treat it as Mixed.");
263             if (request.getRemoteTransition() != null) {
264                 throw new IllegalStateException("Unexpected remote transition in"
265                         + "pip-enter-from-split request");
266             }
267             mActiveTransitions.add(createDefaultMixedTransition(
268                     MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT, transition));
269 
270             WindowContainerTransaction out = new WindowContainerTransaction();
271             mPipHandler.augmentRequest(transition, request, out);
272             mSplitHandler.addEnterOrExitIfNeeded(request, out);
273             return out;
274         } else if (request.getType() == TRANSIT_PIP
275                 && (request.getFlags() & FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY) != 0 && (
276                 mActivityEmbeddingController != null)) {
277             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
278                     " Got a PiP-enter request from an Activity Embedding split");
279             mActiveTransitions.add(createDefaultMixedTransition(
280                     MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING, transition));
281             // Postpone transition splitting to later.
282             WindowContainerTransaction out = new WindowContainerTransaction();
283             return out;
284         } else if (request.getRemoteTransition() != null
285                 && TransitionUtil.isOpeningType(request.getType())
286                 && (request.getTriggerTask() == null
287                 || (request.getTriggerTask().topActivityType != ACTIVITY_TYPE_HOME
288                         && request.getTriggerTask().topActivityType != ACTIVITY_TYPE_RECENTS))) {
289             // Only select transitions with an intent-provided remote-animation because that will
290             // usually grab priority and often won't handle PiP. If there isn't an intent-provided
291             // remote, then the transition will be dispatched normally and the PipHandler will
292             // pick it up.
293             Pair<Transitions.TransitionHandler, WindowContainerTransaction> handler =
294                     mPlayer.dispatchRequest(transition, request, this);
295             if (handler == null) {
296                 return null;
297             }
298             final MixedTransition mixed = createDefaultMixedTransition(
299                     MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE, transition);
300             mixed.mLeftoversHandler = handler.first;
301             mActiveTransitions.add(mixed);
302             if (mixed.mLeftoversHandler != mPlayer.getRemoteTransitionHandler()) {
303                 mixed.mHasRequestToRemote = true;
304                 mPlayer.getRemoteTransitionHandler().handleRequest(transition, request);
305             }
306             return handler.second;
307         } else if (mSplitHandler.isSplitScreenVisible()
308                 && isOpeningType(request.getType())
309                 && request.getTriggerTask() != null
310                 && request.getTriggerTask().getWindowingMode() == WINDOWING_MODE_FULLSCREEN
311                 && request.getTriggerTask().getActivityType() == ACTIVITY_TYPE_HOME) {
312             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a going-home request while "
313                     + "Split-Screen is foreground, so treat it as Mixed.");
314             Pair<Transitions.TransitionHandler, WindowContainerTransaction> handler =
315                     mPlayer.dispatchRequest(transition, request, this);
316             if (handler == null) {
317                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
318                         " Lean on the remote transition handler to fetch a proper remote via"
319                                 + " TransitionFilter");
320                 handler = new Pair<>(
321                         mPlayer.getRemoteTransitionHandler(),
322                         new WindowContainerTransaction());
323             }
324             final MixedTransition mixed = createRecentsMixedTransition(
325                     MixedTransition.TYPE_RECENTS_DURING_SPLIT, transition);
326             mixed.mLeftoversHandler = handler.first;
327             mActiveTransitions.add(mixed);
328             return handler.second;
329         } else if (mUnfoldHandler != null && mUnfoldHandler.shouldPlayUnfoldAnimation(request)) {
330             final WindowContainerTransaction wct =
331                     mUnfoldHandler.handleRequest(transition, request);
332             if (wct != null) {
333                 mActiveTransitions.add(createDefaultMixedTransition(
334                         MixedTransition.TYPE_UNFOLD, transition));
335             }
336             return wct;
337         }
338         return null;
339     }
340 
createDefaultMixedTransition(int type, IBinder transition)341     private DefaultMixedTransition createDefaultMixedTransition(int type, IBinder transition) {
342         return new DefaultMixedTransition(
343                 type, transition, mPlayer, this, mPipHandler, mSplitHandler, mKeyguardHandler,
344                 mUnfoldHandler, mActivityEmbeddingController);
345     }
346 
347     @Override
handleRecentsRequest(WindowContainerTransaction outWCT)348     public Consumer<IBinder> handleRecentsRequest(WindowContainerTransaction outWCT) {
349         if (mRecentsHandler != null) {
350             if (mSplitHandler.isSplitScreenVisible()) {
351                 return this::setRecentsTransitionDuringSplit;
352             } else if (mKeyguardHandler.isKeyguardShowing()) {
353                 return this::setRecentsTransitionDuringKeyguard;
354             } else if (mDesktopTasksController != null
355                     // Check on the default display. Recents/gesture nav is only available there
356                     && mDesktopTasksController.getVisibleTaskCount(DEFAULT_DISPLAY) > 0) {
357                 return this::setRecentsTransitionDuringDesktop;
358             }
359         }
360         return null;
361     }
362 
setRecentsTransitionDuringSplit(IBinder transition)363     private void setRecentsTransitionDuringSplit(IBinder transition) {
364         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while "
365                 + "Split-Screen is foreground, so treat it as Mixed.");
366         mActiveTransitions.add(createRecentsMixedTransition(
367                 MixedTransition.TYPE_RECENTS_DURING_SPLIT, transition));
368     }
369 
setRecentsTransitionDuringKeyguard(IBinder transition)370     private void setRecentsTransitionDuringKeyguard(IBinder transition) {
371         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while "
372                 + "keyguard is visible, so treat it as Mixed.");
373         mActiveTransitions.add(createRecentsMixedTransition(
374                 MixedTransition.TYPE_RECENTS_DURING_KEYGUARD, transition));
375     }
376 
setRecentsTransitionDuringDesktop(IBinder transition)377     private void setRecentsTransitionDuringDesktop(IBinder transition) {
378         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while "
379                 + "desktop mode is active, so treat it as Mixed.");
380         mActiveTransitions.add(createRecentsMixedTransition(
381                 MixedTransition.TYPE_RECENTS_DURING_DESKTOP, transition));
382     }
383 
createRecentsMixedTransition(int type, IBinder transition)384     private MixedTransition createRecentsMixedTransition(int type, IBinder transition) {
385         return new RecentsMixedTransition(type, transition, mPlayer, this, mPipHandler,
386                 mSplitHandler, mKeyguardHandler, mRecentsHandler, mDesktopTasksController);
387     }
388 
subCopy(@onNull TransitionInfo info, @WindowManager.TransitionType int newType, boolean withChanges)389     static TransitionInfo subCopy(@NonNull TransitionInfo info,
390             @WindowManager.TransitionType int newType, boolean withChanges) {
391         final TransitionInfo out = new TransitionInfo(newType, withChanges ? info.getFlags() : 0);
392         out.setTrack(info.getTrack());
393         out.setDebugId(info.getDebugId());
394         if (withChanges) {
395             for (int i = 0; i < info.getChanges().size(); ++i) {
396                 out.getChanges().add(info.getChanges().get(i));
397             }
398         }
399         for (int i = 0; i < info.getRootCount(); ++i) {
400             out.addRoot(info.getRoot(i));
401         }
402         out.setAnimationOptions(info.getAnimationOptions());
403         return out;
404     }
405 
406     @Override
startAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback)407     public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
408             @NonNull SurfaceControl.Transaction startTransaction,
409             @NonNull SurfaceControl.Transaction finishTransaction,
410             @NonNull Transitions.TransitionFinishCallback finishCallback) {
411 
412         MixedTransition mixed = null;
413         for (int i = mActiveTransitions.size() - 1; i >= 0; --i) {
414             if (mActiveTransitions.get(i).mTransition != transition) continue;
415             mixed = mActiveTransitions.get(i);
416             break;
417         }
418 
419         // Offer Keyguard the opportunity to take over lock transitions - ideally we could know by
420         // the time of handleRequest, but we need more information than is available at that time.
421         if (KeyguardTransitionHandler.handles(info)) {
422             if (mixed != null && mixed.mType != MixedTransition.TYPE_KEYGUARD) {
423                 final MixedTransition keyguardMixed =
424                         createDefaultMixedTransition(MixedTransition.TYPE_KEYGUARD, transition);
425                 mActiveTransitions.add(keyguardMixed);
426                 Transitions.TransitionFinishCallback callback = wct -> {
427                     mActiveTransitions.remove(keyguardMixed);
428                     finishCallback.onTransitionFinished(wct);
429                 };
430                 final boolean hasAnimateKeyguard = animateKeyguard(
431                         keyguardMixed, info, startTransaction, finishTransaction, callback,
432                         mKeyguardHandler, mPipHandler);
433                 if (hasAnimateKeyguard) {
434                     ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
435                             "Converting mixed transition into a keyguard transition");
436                     // Consume the original mixed transition
437                     mActiveTransitions.remove(mixed);
438                     mixed.onTransitionConsumed(transition, false, null);
439                     return true;
440                 } else {
441                     // Keyguard handler cannot handle it, process through original mixed
442                     mActiveTransitions.remove(keyguardMixed);
443                 }
444             } else if (mPipHandler != null) {
445                 mPipHandler.syncPipSurfaceState(info, startTransaction, finishTransaction);
446             }
447         }
448 
449         if (mixed == null) return false;
450 
451         final MixedTransition chosenTransition = mixed;
452         Transitions.TransitionFinishCallback callback = wct -> {
453             mActiveTransitions.remove(chosenTransition);
454             finishCallback.onTransitionFinished(wct);
455         };
456 
457         boolean handled = chosenTransition.startAnimation(
458                 transition, info, startTransaction, finishTransaction, callback);
459         if (!handled) {
460             mActiveTransitions.remove(chosenTransition);
461         }
462         return handled;
463     }
464 
unlinkMissingParents(TransitionInfo from)465     private void unlinkMissingParents(TransitionInfo from) {
466         for (int i = 0; i < from.getChanges().size(); ++i) {
467             final TransitionInfo.Change chg = from.getChanges().get(i);
468             if (chg.getParent() == null) continue;
469             if (from.getChange(chg.getParent()) == null) {
470                 from.getChanges().get(i).setParent(null);
471             }
472         }
473     }
474 
isWithinTask(TransitionInfo info, TransitionInfo.Change chg)475     private boolean isWithinTask(TransitionInfo info, TransitionInfo.Change chg) {
476         TransitionInfo.Change curr = chg;
477         while (curr != null) {
478             if (curr.getTaskInfo() != null) return true;
479             if (curr.getParent() == null) break;
480             curr = info.getChange(curr.getParent());
481         }
482         return false;
483     }
484 
485     /**
486      * This is intended to be called by SplitCoordinator as a helper to mix a split handling
487      * transition with an entering-pip change. The use-case for this is when an auto-pip change
488      * gets collected into the transition which has already claimed by
489      * StageCoordinator.handleRequest. This happens when launching a fullscreen app while having an
490      * auto-pip activity in the foreground split pair.
491      */
492     // TODO(b/287704263): Remove when split/mixed are reversed.
animatePendingEnterPipFromSplit(IBinder transition, TransitionInfo info, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, Transitions.TransitionFinishCallback finishCallback, boolean replacingPip)493     public boolean animatePendingEnterPipFromSplit(IBinder transition, TransitionInfo info,
494             SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
495             Transitions.TransitionFinishCallback finishCallback, boolean replacingPip) {
496         int type = replacingPip
497                 ? MixedTransition.TYPE_ENTER_PIP_REPLACE_FROM_SPLIT
498                 : MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT;
499         final MixedTransition mixed = createDefaultMixedTransition(type, transition);
500         mActiveTransitions.add(mixed);
501         Transitions.TransitionFinishCallback callback = wct -> {
502             mActiveTransitions.remove(mixed);
503             finishCallback.onTransitionFinished(wct);
504         };
505         return mixed.startAnimation(transition, info, startT, finishT, callback);
506     }
507 
508     /**
509      * This is intended to be called by SplitCoordinator as a helper to mix an already-pending
510      * split transition with a display-change. The use-case for this is when a display
511      * change/rotation gets collected into a split-screen enter/exit transition which has already
512      * been claimed by StageCoordinator.handleRequest. This happens during launcher tests.
513      */
animatePendingSplitWithDisplayChange(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startT, @NonNull SurfaceControl.Transaction finishT, @NonNull Transitions.TransitionFinishCallback finishCallback)514     public boolean animatePendingSplitWithDisplayChange(@NonNull IBinder transition,
515             @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startT,
516             @NonNull SurfaceControl.Transaction finishT,
517             @NonNull Transitions.TransitionFinishCallback finishCallback) {
518         final TransitionInfo everythingElse = subCopy(info, info.getType(), true /* withChanges */);
519         final TransitionInfo displayPart = subCopy(info, TRANSIT_CHANGE, false /* withChanges */);
520         for (int i = info.getChanges().size() - 1; i >= 0; --i) {
521             TransitionInfo.Change change = info.getChanges().get(i);
522             if (isWithinTask(info, change)) continue;
523             displayPart.addChange(change);
524             everythingElse.getChanges().remove(i);
525         }
526         if (displayPart.getChanges().isEmpty()) return false;
527         unlinkMissingParents(everythingElse);
528         final MixedTransition mixed = createDefaultMixedTransition(
529                 MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE, transition);
530         mActiveTransitions.add(mixed);
531         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animation is a mix of display change "
532                 + "and split change.");
533         // We need to split the transition into 2 parts: the split part and the display part.
534         mixed.mInFlightSubAnimations = 2;
535 
536         Transitions.TransitionFinishCallback finishCB = (wct) -> {
537             --mixed.mInFlightSubAnimations;
538             mixed.joinFinishArgs(wct);
539             if (mixed.mInFlightSubAnimations > 0) return;
540             mActiveTransitions.remove(mixed);
541             finishCallback.onTransitionFinished(mixed.mFinishWCT);
542         };
543 
544         // Dispatch the display change. This will most-likely be taken by the default handler.
545         // Do this first since the first handler used will apply the startT; the display change
546         // needs to take a screenshot before that happens so we need it to be the first handler.
547         mixed.mLeftoversHandler = mPlayer.dispatchTransition(mixed.mTransition, displayPart,
548                 startT, finishT, finishCB, mSplitHandler);
549 
550         // Note: at this point, startT has probably already been applied, so we are basically
551         // giving splitHandler an empty startT. This is currently OK because display-change will
552         // grab a screenshot and paste it on top anyways.
553         mSplitHandler.startPendingAnimation(transition, everythingElse, startT, finishT, finishCB);
554         return true;
555     }
556 
557     /**
558      * For example: pip is entering in rotation 0, and then the display changes to rotation 90
559      * before the pip transition is ready. So the info contains both the entering pip and display
560      * change. In this case, the pip can go to the end state in new rotation directly, and let the
561      * display level animation cover all changed participates.
562      */
animateEnteringPipWithDisplayChange(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull TransitionInfo.Change pipChange, @NonNull SurfaceControl.Transaction startT, @NonNull SurfaceControl.Transaction finishT, @NonNull Transitions.TransitionFinishCallback finishCallback)563     public void animateEnteringPipWithDisplayChange(@NonNull IBinder transition,
564             @NonNull TransitionInfo info, @NonNull TransitionInfo.Change pipChange,
565             @NonNull SurfaceControl.Transaction startT,
566             @NonNull SurfaceControl.Transaction finishT,
567             @NonNull Transitions.TransitionFinishCallback finishCallback) {
568         // In order to play display level animation, force the type to CHANGE (it could be PIP).
569         final TransitionInfo changeInfo = info.getType() != TRANSIT_CHANGE
570                 ? subCopy(info, TRANSIT_CHANGE, true /* withChanges */) : info;
571         final MixedTransition mixed = createDefaultMixedTransition(
572                 MixedTransition.TYPE_ENTER_PIP_WITH_DISPLAY_CHANGE, transition);
573         mActiveTransitions.add(mixed);
574         mixed.mInFlightSubAnimations = 2;
575         final Transitions.TransitionFinishCallback finishCB = wct -> {
576             --mixed.mInFlightSubAnimations;
577             mixed.joinFinishArgs(wct);
578             if (mixed.mInFlightSubAnimations > 0) return;
579             mActiveTransitions.remove(mixed);
580             finishCallback.onTransitionFinished(mixed.mFinishWCT);
581         };
582         // Perform the display animation first.
583         mixed.mLeftoversHandler = mPlayer.dispatchTransition(mixed.mTransition, changeInfo,
584                 startT, finishT, finishCB, mPipHandler);
585         // Use a standalone finish transaction for pip because it will apply immediately.
586         final SurfaceControl.Transaction pipFinishT = new SurfaceControl.Transaction();
587         mPipHandler.startEnterAnimation(pipChange, startT, pipFinishT, wct -> {
588             // Apply immediately to avoid potential flickering by bounds change at the end of
589             // display animation.
590             mPipHandler.applyTransaction(wct);
591             finishCB.onTransitionFinished(null /* wct */);
592         });
593         // Jump to the pip end state directly and make sure the real finishT have the latest state.
594         mPipHandler.end();
595         mPipHandler.syncPipSurfaceState(info, startT, finishT);
596     }
597 
animateKeyguard(@onNull final MixedTransition mixed, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback, @NonNull KeyguardTransitionHandler keyguardHandler, PipTransitionController pipHandler)598     private static boolean animateKeyguard(@NonNull final MixedTransition mixed,
599             @NonNull TransitionInfo info,
600             @NonNull SurfaceControl.Transaction startTransaction,
601             @NonNull SurfaceControl.Transaction finishTransaction,
602             @NonNull Transitions.TransitionFinishCallback finishCallback,
603             @NonNull KeyguardTransitionHandler keyguardHandler,
604             PipTransitionController pipHandler) {
605         if (mixed.mFinishT == null) {
606             mixed.mFinishT = finishTransaction;
607             mixed.mFinishCB = finishCallback;
608         }
609         // Sync pip state.
610         if (pipHandler != null) {
611             pipHandler.syncPipSurfaceState(info, startTransaction, finishTransaction);
612         }
613         return mixed.startSubAnimation(keyguardHandler, info, startTransaction, finishTransaction);
614     }
615 
616     /** Use to when split use intent to enter, check if this enter transition should be mixed or
617      * not.*/
isIntentInPip(PendingIntent intent)618     public boolean isIntentInPip(PendingIntent intent) {
619         // Check if this intent package is same as pip one or not, if true we want let the pip
620         // task enter split.
621         if (mPipHandler != null) {
622             return mPipHandler
623                     .isPackageActiveInPip(SplitScreenUtils.getPackageName(intent.getIntent()));
624         }
625         return false;
626     }
627 
628     /** Use to when split use taskId to enter, check if this enter transition should be mixed or
629      * not.*/
isTaskInPip(int taskId, ShellTaskOrganizer shellTaskOrganizer)630     public boolean isTaskInPip(int taskId, ShellTaskOrganizer shellTaskOrganizer) {
631         // Check if this intent package is same as pip one or not, if true we want let the pip
632         // task enter split.
633         if (mPipHandler != null) {
634             return mPipHandler.isPackageActiveInPip(
635                     SplitScreenUtils.getPackageName(taskId, shellTaskOrganizer));
636         }
637         return false;
638     }
639 
640     /** @return whether the transition-request represents a pip-entry. */
requestHasPipEnter(TransitionRequestInfo request)641     public boolean requestHasPipEnter(TransitionRequestInfo request) {
642         return mPipHandler.requestHasPipEnter(request);
643     }
644 
645     /** Whether a particular change is a window that is entering pip. */
646     // TODO(b/287704263): Remove when split/mixed are reversed.
isEnteringPip(TransitionInfo.Change change, @WindowManager.TransitionType int transitType)647     public boolean isEnteringPip(TransitionInfo.Change change,
648             @WindowManager.TransitionType int transitType) {
649         return mPipHandler.isEnteringPip(change, transitType);
650     }
651 
652     @Override
mergeAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, @NonNull Transitions.TransitionFinishCallback finishCallback)653     public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
654             @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
655             @NonNull Transitions.TransitionFinishCallback finishCallback) {
656         for (int i = 0; i < mActiveTransitions.size(); ++i) {
657             if (mActiveTransitions.get(i).mTransition != mergeTarget) continue;
658 
659             MixedTransition mixed = mActiveTransitions.get(i);
660             if (mixed.mInFlightSubAnimations <= 0) {
661                 // Already done, so no need to end it.
662                 return;
663             }
664             mixed.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
665         }
666     }
667 
668     @Override
onTransitionConsumed(@onNull IBinder transition, boolean aborted, @Nullable SurfaceControl.Transaction finishT)669     public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
670             @Nullable SurfaceControl.Transaction finishT) {
671         MixedTransition mixed = null;
672         for (int i = mActiveTransitions.size() - 1; i >= 0; --i) {
673             if (mActiveTransitions.get(i).mTransition != transition) continue;
674             mixed = mActiveTransitions.remove(i);
675             break;
676         }
677         if (mixed != null) {
678             mixed.onTransitionConsumed(transition, aborted, finishT);
679         }
680     }
681 
682     /**
683      * Update an incoming {@link TransitionInfo} with the leashes from an existing
684      * {@link TransitionInfo} so that it can take over some parts of the animation without
685      * reparenting to new transition roots.
686      */
handoverTransitionLeashes( @onNull TransitionInfo from, @NonNull TransitionInfo to, @NonNull SurfaceControl.Transaction startT, @NonNull SurfaceControl.Transaction finishT)687     static void handoverTransitionLeashes(
688             @NonNull TransitionInfo from,
689             @NonNull TransitionInfo to,
690             @NonNull SurfaceControl.Transaction startT,
691             @NonNull SurfaceControl.Transaction finishT) {
692 
693         // Show the roots in case they contain new changes not present in the original transition.
694         for (int j = to.getRootCount() - 1; j >= 0; --j) {
695             startT.show(to.getRoot(j).getLeash());
696         }
697 
698         // Find all of the leashes from the original transition.
699         Map<WindowContainerToken, TransitionInfo.Change> originalChanges = new ArrayMap<>();
700         for (TransitionInfo.Change oldChange : from.getChanges()) {
701             if (oldChange.getContainer() != null) {
702                 originalChanges.put(oldChange.getContainer(), oldChange);
703             }
704         }
705 
706         // Merge the animation leashes by re-using the original ones if we see the same container
707         // in the new transition and the old.
708         for (TransitionInfo.Change newChange : to.getChanges()) {
709             if (originalChanges.containsKey(newChange.getContainer())) {
710                 final TransitionInfo.Change oldChange = originalChanges.get(
711                         newChange.getContainer());
712                 startT.reparent(newChange.getLeash(), null);
713                 newChange.setLeash(oldChange.getLeash());
714             }
715         }
716     }
717 }
718