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