• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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.recents;
18 
19 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
20 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
21 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
22 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
23 import static android.view.Display.DEFAULT_DISPLAY;
24 import static android.view.WindowManager.KEYGUARD_VISIBILITY_TRANSIT_FLAGS;
25 import static android.view.WindowManager.TRANSIT_CHANGE;
26 import static android.view.WindowManager.TRANSIT_CLOSE;
27 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
28 import static android.view.WindowManager.TRANSIT_OPEN;
29 import static android.view.WindowManager.TRANSIT_PIP;
30 import static android.view.WindowManager.TRANSIT_SLEEP;
31 import static android.view.WindowManager.TRANSIT_TO_FRONT;
32 import static android.window.DesktopModeFlags.ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX;
33 import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
34 import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
35 
36 import static com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_ANIMATING;
37 import static com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_NOT_RUNNING;
38 import static com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_REQUESTED;
39 import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_CAN_HAND_OFF_ANIMATION;
40 import static com.android.wm.shell.shared.split.SplitBounds.KEY_EXTRA_SPLIT_BOUNDS;
41 import static com.android.wm.shell.transition.Transitions.TRANSIT_END_RECENTS_TRANSITION;
42 import static com.android.wm.shell.transition.Transitions.TRANSIT_START_RECENTS_TRANSITION;
43 
44 import android.annotation.Nullable;
45 import android.annotation.SuppressLint;
46 import android.app.ActivityManager;
47 import android.app.ActivityTaskManager;
48 import android.app.IApplicationThread;
49 import android.app.PendingIntent;
50 import android.app.WindowConfiguration;
51 import android.content.Context;
52 import android.content.Intent;
53 import android.graphics.Color;
54 import android.graphics.Rect;
55 import android.os.Binder;
56 import android.os.Bundle;
57 import android.os.IBinder;
58 import android.os.RemoteException;
59 import android.util.ArrayMap;
60 import android.util.IntArray;
61 import android.util.Pair;
62 import android.util.Slog;
63 import android.view.RemoteAnimationTarget;
64 import android.view.SurfaceControl;
65 import android.window.PictureInPictureSurfaceTransaction;
66 import android.window.TaskSnapshot;
67 import android.window.TransitionInfo;
68 import android.window.TransitionRequestInfo;
69 import android.window.WindowAnimationState;
70 import android.window.WindowContainerToken;
71 import android.window.WindowContainerTransaction;
72 
73 import androidx.annotation.NonNull;
74 
75 import com.android.internal.annotations.VisibleForTesting;
76 import com.android.internal.os.IResultReceiver;
77 import com.android.internal.protolog.ProtoLog;
78 import com.android.wm.shell.Flags;
79 import com.android.wm.shell.ShellTaskOrganizer;
80 import com.android.wm.shell.common.ShellExecutor;
81 import com.android.wm.shell.common.pip.PipUtils;
82 import com.android.wm.shell.protolog.ShellProtoLogGroup;
83 import com.android.wm.shell.shared.R;
84 import com.android.wm.shell.shared.TransitionUtil;
85 import com.android.wm.shell.sysui.ShellInit;
86 import com.android.wm.shell.transition.HomeTransitionObserver;
87 import com.android.wm.shell.transition.Transitions;
88 
89 import java.util.ArrayList;
90 import java.util.function.Consumer;
91 
92 /**
93  * Handles the Recents (overview) animation. Only one of these can run at a time. A recents
94  * transition must be created via {@link #startRecentsTransition}. Anything else will be ignored.
95  */
96 public class RecentsTransitionHandler implements Transitions.TransitionHandler,
97         Transitions.TransitionObserver {
98     private static final String TAG = "RecentsTransitionHandler";
99 
100     // A placeholder for a synthetic transition that isn't backed by a true system transition
101     public static final IBinder SYNTHETIC_TRANSITION = new Binder();
102 
103     private final Transitions mTransitions;
104     private final ShellTaskOrganizer mShellTaskOrganizer;
105     private final ShellExecutor mExecutor;
106     @Nullable
107     private final RecentTasksController mRecentTasksController;
108     private IApplicationThread mAnimApp = null;
109     private final ArrayList<RecentsController> mControllers = new ArrayList<>();
110     private final ArrayList<RecentsTransitionStateListener> mStateListeners = new ArrayList<>();
111 
112     /**
113      * List of other handlers which might need to mix recents with other things. These are checked
114      * in the order they are added. Ideally there should only be one.
115      */
116     private final ArrayList<RecentsMixedHandler> mMixers = new ArrayList<>();
117 
118     private final HomeTransitionObserver mHomeTransitionObserver;
119     private @Nullable Color mBackgroundColor;
120 
RecentsTransitionHandler( @onNull ShellInit shellInit, @NonNull ShellTaskOrganizer shellTaskOrganizer, @NonNull Transitions transitions, @Nullable RecentTasksController recentTasksController, @NonNull HomeTransitionObserver homeTransitionObserver)121     public RecentsTransitionHandler(
122             @NonNull ShellInit shellInit,
123             @NonNull ShellTaskOrganizer shellTaskOrganizer,
124             @NonNull Transitions transitions,
125             @Nullable RecentTasksController recentTasksController,
126             @NonNull HomeTransitionObserver homeTransitionObserver) {
127         mShellTaskOrganizer = shellTaskOrganizer;
128         mTransitions = transitions;
129         mExecutor = transitions.getMainExecutor();
130         mRecentTasksController = recentTasksController;
131         mHomeTransitionObserver = homeTransitionObserver;
132         if (!Transitions.ENABLE_SHELL_TRANSITIONS) return;
133         if (recentTasksController == null) return;
134         shellInit.addInitCallback(this::onInit, this);
135     }
136 
onInit()137     private void onInit() {
138         mRecentTasksController.setTransitionHandler(this);
139         mTransitions.addHandler(this);
140         mTransitions.registerObserver(this);
141     }
142 
143     /** Register a mixer handler. {@see RecentsMixedHandler}*/
addMixer(RecentsMixedHandler mixer)144     public void addMixer(RecentsMixedHandler mixer) {
145         mMixers.add(mixer);
146     }
147 
148     /** Unregister a Mixed Handler */
removeMixer(RecentsMixedHandler mixer)149     public void removeMixer(RecentsMixedHandler mixer) {
150         mMixers.remove(mixer);
151     }
152 
153     /** Adds the callback for receiving the state change of transition. */
addTransitionStateListener(RecentsTransitionStateListener listener)154     public void addTransitionStateListener(RecentsTransitionStateListener listener) {
155         mStateListeners.add(listener);
156     }
157 
158     /**
159      * Sets a background color on the transition root layered behind the outgoing task. {@code null}
160      * may be used to clear any previously set colors to avoid showing a background at all. The
161      * color is always shown at full opacity.
162      */
setTransitionBackgroundColor(@ullable Color color)163     public void setTransitionBackgroundColor(@Nullable Color color) {
164         mBackgroundColor = color;
165     }
166 
167     /**
168      * Starts a new real/synthetic recents transition.
169      */
170     @VisibleForTesting
startRecentsTransition(PendingIntent intent, Intent fillIn, Bundle options, IApplicationThread appThread, IRecentsAnimationRunner listener)171     public IBinder startRecentsTransition(PendingIntent intent, Intent fillIn, Bundle options,
172             IApplicationThread appThread, IRecentsAnimationRunner listener) {
173         // only care about latest one.
174         mAnimApp = appThread;
175 
176         for (int i = 0; i < mStateListeners.size(); i++) {
177             mStateListeners.get(i).onTransitionStateChanged(TRANSITION_STATE_REQUESTED);
178         }
179         // TODO(b/366021931): Formalize this later
180         final boolean isSyntheticRequest = options.getBoolean(
181                 "is_synthetic_recents_transition", /* defaultValue= */ false);
182         final IBinder transition;
183         if (isSyntheticRequest) {
184             transition = startSyntheticRecentsTransition(listener);
185         } else {
186             transition = startRealRecentsTransition(intent, fillIn, options, listener);
187         }
188         return transition;
189     }
190 
191     /**
192      * Starts a synthetic recents transition that is not backed by a real WM transition.
193      */
startSyntheticRecentsTransition(@onNull IRecentsAnimationRunner listener)194     private IBinder startSyntheticRecentsTransition(@NonNull IRecentsAnimationRunner listener) {
195         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
196                 "RecentsTransitionHandler.startRecentsTransition(synthetic)");
197         final RecentsController lastController = getLastController();
198         if (lastController != null) {
199             lastController.cancel(lastController.isSyntheticTransition()
200                     ? "existing_running_synthetic_transition"
201                     : "existing_running_transition");
202             return null;
203         }
204 
205         // Create a new synthetic transition and start it immediately
206         final RecentsController controller = new RecentsController(listener);
207         controller.startSyntheticTransition();
208         mControllers.add(controller);
209         return SYNTHETIC_TRANSITION;
210     }
211 
212     /**
213      * Starts a real WM-backed recents transition.
214      */
startRealRecentsTransition(PendingIntent intent, Intent fillIn, Bundle options, IRecentsAnimationRunner listener)215     private IBinder startRealRecentsTransition(PendingIntent intent, Intent fillIn, Bundle options,
216             IRecentsAnimationRunner listener) {
217         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
218                 "RecentsTransitionHandler.startRecentsTransition");
219 
220         final WindowContainerTransaction wct = new WindowContainerTransaction();
221         wct.sendPendingIntent(intent, fillIn, options);
222 
223         // Find the mixed handler which should handle this request (if we are in a state where a
224         // mixed handler is needed).  This is slightly convoluted because starting the transition
225         // requires the handler, but the mixed handler also needs a reference to the transition.
226         RecentsMixedHandler mixer = null;
227         Consumer<IBinder> setTransitionForMixer = null;
228         for (int i = 0; i < mMixers.size(); ++i) {
229             setTransitionForMixer = mMixers.get(i).handleRecentsRequest();
230             if (setTransitionForMixer != null) {
231                 mixer = mMixers.get(i);
232                 break;
233             }
234         }
235         final int transitionType = Flags.enableRecentsBookendTransition()
236                 ? TRANSIT_START_RECENTS_TRANSITION
237                 : TRANSIT_TO_FRONT;
238         final IBinder transition = mTransitions.startTransition(transitionType,
239                 wct, mixer == null ? this : mixer);
240         if (mixer != null) {
241             setTransitionForMixer.accept(transition);
242         }
243 
244         final RecentsController controller = new RecentsController(listener);
245         if (transition != null) {
246             controller.setTransition(transition);
247             mControllers.add(controller);
248         } else {
249             controller.cancel("startRecentsTransition");
250         }
251         return transition;
252     }
253 
254     @Override
handleRequest(IBinder transition, TransitionRequestInfo request)255     public WindowContainerTransaction handleRequest(IBinder transition,
256             TransitionRequestInfo request) {
257         if (mControllers.isEmpty()) {
258             // Ignore if there is no running recents transition
259             return null;
260         }
261         final RecentsController controller = mControllers.get(mControllers.size() - 1);
262         controller.handleMidTransitionRequest(request);
263         return null;
264     }
265 
266     /**
267      * Returns if there is currently a pending or active recents transition.
268      */
269     @Nullable
getLastController()270     private RecentsController getLastController() {
271         return !mControllers.isEmpty() ? mControllers.getLast() : null;
272     }
273 
274     /**
275      * Finds an existing controller for the provided {@param transition}, or {@code null} if none
276      * exists.
277      */
278     @Nullable
279     @VisibleForTesting
findController(@onNull IBinder transition)280     RecentsController findController(@NonNull IBinder transition) {
281         for (int i = mControllers.size() - 1; i >= 0; --i) {
282             final RecentsController controller = mControllers.get(i);
283             if (controller.mTransition == transition) {
284                 return controller;
285             }
286         }
287         return null;
288     }
289 
290     @Override
startAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction startTransaction, SurfaceControl.Transaction finishTransaction, Transitions.TransitionFinishCallback finishCallback)291     public boolean startAnimation(IBinder transition, TransitionInfo info,
292             SurfaceControl.Transaction startTransaction,
293             SurfaceControl.Transaction finishTransaction,
294             Transitions.TransitionFinishCallback finishCallback) {
295         final RecentsController controller = findController(transition);
296         if (controller == null) {
297             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
298                     "RecentsTransitionHandler.startAnimation: no controller found");
299             return false;
300         }
301         final IApplicationThread animApp = mAnimApp;
302         mAnimApp = null;
303         if (!controller.start(info, startTransaction, finishTransaction, finishCallback)) {
304             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
305                     "RecentsTransitionHandler.startAnimation: failed to start animation");
306             return false;
307         }
308         Transitions.setRunningRemoteTransitionDelegate(animApp);
309         return true;
310     }
311 
312     @Override
mergeAnimation(IBinder transition, TransitionInfo info, @NonNull SurfaceControl.Transaction startT, @NonNull SurfaceControl.Transaction finishT, IBinder mergeTarget, Transitions.TransitionFinishCallback finishCallback)313     public void mergeAnimation(IBinder transition, TransitionInfo info,
314             @NonNull SurfaceControl.Transaction startT,
315             @NonNull SurfaceControl.Transaction finishT,
316             IBinder mergeTarget,
317             Transitions.TransitionFinishCallback finishCallback) {
318         final RecentsController controller = findController(mergeTarget);
319         if (controller == null) {
320             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
321                     "RecentsTransitionHandler.mergeAnimation: no controller found");
322             return;
323         }
324         controller.merge(info, startT, finishT, finishCallback);
325     }
326 
327     @Override
onTransitionConsumed(IBinder transition, boolean aborted, SurfaceControl.Transaction finishTransaction)328     public void onTransitionConsumed(IBinder transition, boolean aborted,
329             SurfaceControl.Transaction finishTransaction) {
330         // Only one recents transition can be handled at a time, but currently the first transition
331         // will trigger a no-op in the second transition which holds the active recents animation
332         // runner on the launcher side.  For now, cancel all existing animations to ensure we
333         // don't get into a broken state with an orphaned animation runner, and later we can try to
334         // merge the latest transition into the currently running one
335         for (int i = mControllers.size() - 1; i >= 0; i--) {
336             mControllers.get(i).cancel("onTransitionConsumed");
337         }
338     }
339 
340     @Override
onTransitionReady(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction)341     public void onTransitionReady(@NonNull IBinder transition, @NonNull TransitionInfo info,
342             @NonNull SurfaceControl.Transaction startTransaction,
343             @NonNull SurfaceControl.Transaction finishTransaction) {
344         RecentsController controller = findController(SYNTHETIC_TRANSITION);
345         if (controller != null) {
346             // Cancel the existing synthetic transition if there is one
347             controller.cancel("incoming_transition");
348         }
349     }
350 
351     /** There is only one of these and it gets reset on finish. */
352     @VisibleForTesting
353     class RecentsController extends IRecentsAnimationController.Stub {
354 
355         private final int mInstanceId;
356 
357         private IRecentsAnimationRunner mListener;
358         private IBinder.DeathRecipient mDeathHandler;
359         private Transitions.TransitionFinishCallback mFinishCB = null;
360         private SurfaceControl.Transaction mFinishTransaction = null;
361 
362         /**
363          * List of tasks that we are switching away from via this transition. Upon finish, these
364          * pausing tasks will become invisible.
365          * These need to be ordered since the order must be restored if there is no task-switch.
366          */
367         private ArrayList<TaskState> mPausingTasks = null;
368 
369         /**
370          * List of tasks were pausing but closed in a subsequent merged transition. If a
371          * closing task is reopened, the leash is not initially hidden since it is already
372          * visible.
373          */
374         private ArrayList<TaskState> mClosingTasks = null;
375 
376         /**
377          * List of tasks that we are switching to. Upon finish, these will remain visible and
378          * on top.
379          */
380         private ArrayList<TaskState> mOpeningTasks = null;
381 
382         private WindowContainerToken mPipTask = null;
383         private int mPipTaskId = -1;
384         private WindowContainerToken mRecentsTask = null;
385         private int mRecentsTaskId = -1;
386         private TransitionInfo mInfo = null;
387         private boolean mOpeningSeparateHome = false;
388         private boolean mPausingSeparateHome = false;
389         private ArrayMap<SurfaceControl, SurfaceControl> mLeashMap = null;
390         private PictureInPictureSurfaceTransaction mPipTransaction = null;
391         // This is the transition that backs the entire recents transition, and the one that the
392         // pending finish transition below will be merged into
393         private IBinder mTransition = null;
394         private boolean mKeyguardLocked = false;
395         private boolean mWillFinishToHome = false;
396         private Transitions.TransitionHandler mTakeoverHandler = null;
397 
398         /** The animation is idle, waiting for the user to choose a task to switch to. */
399         private static final int STATE_NORMAL = 0;
400 
401         /** The user chose a new task to switch to and the animation is animating to it. */
402         private static final int STATE_NEW_TASK = 1;
403 
404         /** The latest state that the recents animation is operating in. */
405         private int mState = STATE_NORMAL;
406 
407         // Snapshots taken when a new display change transition is requested, prior to the display
408         // change being applied.  This pending set of snapshots will only be applied when cancel is
409         // next called.
410         private Pair<int[], TaskSnapshot[]> mPendingPauseSnapshotsForCancel;
411 
412         // Used to track a pending finish transition, this is only non-null if
413         // enableRecentsBookendTransition() is enabled
414         private IBinder mPendingFinishTransition;
415         private IResultReceiver mPendingRunnerFinishCb;
416 
RecentsController(IRecentsAnimationRunner listener)417         RecentsController(IRecentsAnimationRunner listener) {
418             mInstanceId = System.identityHashCode(this);
419             mListener = listener;
420             mDeathHandler = () -> {
421                 mExecutor.execute(() -> {
422                     ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
423                             "[%d] RecentsController.DeathRecipient: binder died", mInstanceId);
424                     finishInner(mWillFinishToHome, false /* leaveHint */, null /* finishCb */,
425                             "deathRecipient");
426                 });
427             };
428             try {
429                 mListener.asBinder().linkToDeath(mDeathHandler, 0 /* flags */);
430             } catch (RemoteException e) {
431                 Slog.e(TAG, "RecentsController: failed to link to death", e);
432                 mListener = null;
433             }
434         }
435 
436         /**
437          * Sets the started transition for this instance of the recents transition.
438          */
setTransition(IBinder transition)439         void setTransition(IBinder transition) {
440             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
441                     "[%d] RecentsController.setTransition: id=%s", mInstanceId, transition);
442             mTransition = transition;
443         }
444 
cancel(String reason)445         void cancel(String reason) {
446             // restoring (to-home = false) involves submitting more WM changes, so by default, use
447             // toHome = true when canceling.
448             cancel(true /* toHome */, false /* withScreenshots */, reason);
449         }
450 
cancel(boolean toHome, boolean withScreenshots, String reason)451         void cancel(boolean toHome, boolean withScreenshots, String reason) {
452             if (cancelSyntheticTransition(reason)) {
453                 return;
454             }
455 
456             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
457                     "[%d] RecentsController.cancel: toHome=%b reason=%s",
458                     mInstanceId, toHome, reason);
459             if (mListener != null) {
460                 if (withScreenshots) {
461                     sendCancelWithSnapshots();
462                 } else {
463                     sendCancel(null, null);
464                 }
465             }
466             if (mFinishCB != null) {
467                 finishInner(toHome, false /* userLeave */, null /* finishCb */, "cancel");
468             } else {
469                 cleanUp();
470             }
471         }
472 
473         /**
474          * Sends a cancel message to the recents animation with snapshots. Used to trigger a
475          * "replace-with-screenshot" like behavior.
476          */
sendCancelWithSnapshots()477         private boolean sendCancelWithSnapshots() {
478             Pair<int[], TaskSnapshot[]> snapshots = mPendingPauseSnapshotsForCancel != null
479                     ? mPendingPauseSnapshotsForCancel
480                     : getSnapshotsForPausingTasks();
481             return sendCancel(snapshots.first, snapshots.second);
482         }
483 
484         /**
485          * Snapshots the pausing tasks and returns the mapping of the taskId -> snapshot.
486          */
getSnapshotsForPausingTasks()487         private Pair<int[], TaskSnapshot[]> getSnapshotsForPausingTasks() {
488             int[] taskIds = null;
489             TaskSnapshot[] snapshots = null;
490             if (mPausingTasks != null && mPausingTasks.size() > 0) {
491                 taskIds = new int[mPausingTasks.size()];
492                 snapshots = new TaskSnapshot[mPausingTasks.size()];
493                 try {
494                     for (int i = 0; i < mPausingTasks.size(); ++i) {
495                         TaskState state = mPausingTasks.get(0);
496                         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
497                                 "[%d] RecentsController.sendCancel: Snapshotting task=%d",
498                                 mInstanceId, state.mTaskInfo.taskId);
499                         snapshots[i] = ActivityTaskManager.getService().takeTaskSnapshot(
500                                 state.mTaskInfo.taskId, true /* updateCache */);
501                     }
502                 } catch (RemoteException e) {
503                     taskIds = null;
504                     snapshots = null;
505                 }
506             }
507             return new Pair(taskIds, snapshots);
508         }
509 
510         /**
511          * Sends a cancel message to the recents animation.
512          */
sendCancel(@ullable int[] taskIds, @Nullable TaskSnapshot[] taskSnapshots)513         private boolean sendCancel(@Nullable int[] taskIds,
514                 @Nullable TaskSnapshot[] taskSnapshots) {
515             try {
516                 final String cancelDetails = taskSnapshots != null ? "with snapshots" : "";
517                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
518                         "[%d] RecentsController.cancel: calling onAnimationCanceled %s",
519                         mInstanceId, cancelDetails);
520                 mListener.onAnimationCanceled(taskIds, taskSnapshots);
521                 return true;
522             } catch (RemoteException e) {
523                 Slog.e(TAG, "Error canceling recents animation", e);
524                 return false;
525             }
526         }
527 
528         /**
529          * Cleans up the recents transition.  This should generally not be called directly
530          * to cancel a transition after it has started, instead callers should call one of
531          * the cancel() methods to ensure that Launcher is notified.
532          */
cleanUp()533         void cleanUp() {
534             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
535                     "[%d] RecentsController.cleanup", mInstanceId);
536             if (mListener != null && mDeathHandler != null) {
537                 mListener.asBinder().unlinkToDeath(mDeathHandler, 0 /* flags */);
538                 mDeathHandler = null;
539             }
540             mListener = null;
541             mFinishCB = null;
542             // clean-up leash surfacecontrols and anything that might reference them.
543             if (mLeashMap != null) {
544                 for (int i = 0; i < mLeashMap.size(); ++i) {
545                     mLeashMap.valueAt(i).release();
546                 }
547                 mLeashMap = null;
548             }
549             mFinishTransaction = null;
550             mPausingTasks = null;
551             mClosingTasks = null;
552             mOpeningTasks = null;
553             mInfo = null;
554             mTransition = null;
555             mPendingPauseSnapshotsForCancel = null;
556             mPipTaskId = -1;
557             mPipTask = null;
558             mPipTransaction = null;
559             mPendingRunnerFinishCb = null;
560             mPendingFinishTransition = null;
561             mControllers.remove(this);
562             for (int i = 0; i < mStateListeners.size(); i++) {
563                 mStateListeners.get(i).onTransitionStateChanged(TRANSITION_STATE_NOT_RUNNING);
564             }
565         }
566 
567         /**
568          * Starts a new transition that is not backed by a system transition.
569          */
startSyntheticTransition()570         void startSyntheticTransition() {
571             mTransition = SYNTHETIC_TRANSITION;
572 
573             // TODO(b/366021931): Update mechanism for pulling the home task, for now add home as
574             //                    both opening and closing since there's some pre-existing
575             //                    dependencies on having a closing task
576             final ActivityManager.RunningTaskInfo homeTask =
577                     mShellTaskOrganizer.getRunningTasks(DEFAULT_DISPLAY).stream()
578                             .filter(task -> task.getActivityType() == ACTIVITY_TYPE_HOME)
579                             .findFirst()
580                             .get();
581             final RemoteAnimationTarget openingTarget = TransitionUtil.newSyntheticTarget(
582                     homeTask, mShellTaskOrganizer.getHomeTaskSurface(), TRANSIT_OPEN,
583                     0, true /* isTranslucent */);
584             final RemoteAnimationTarget closingTarget = TransitionUtil.newSyntheticTarget(
585                     homeTask, mShellTaskOrganizer.getHomeTaskSurface(), TRANSIT_CLOSE,
586                     0, true /* isTranslucent */);
587             final ArrayList<RemoteAnimationTarget> apps = new ArrayList<>();
588             apps.add(openingTarget);
589             apps.add(closingTarget);
590             try {
591                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
592                         "[%d] RecentsController.start: calling onAnimationStart with %d apps",
593                         mInstanceId, apps.size());
594                 mListener.onAnimationStart(this,
595                         apps.toArray(new RemoteAnimationTarget[apps.size()]),
596                         new RemoteAnimationTarget[0],
597                         new Rect(0, 0, 0, 0), new Rect(), new Bundle(),
598                         null);
599                 for (int i = 0; i < mStateListeners.size(); i++) {
600                     mStateListeners.get(i).onTransitionStateChanged(TRANSITION_STATE_ANIMATING);
601                 }
602             } catch (RemoteException e) {
603                 Slog.e(TAG, "Error starting recents animation", e);
604                 cancel("startSynthetricTransition() failed");
605             }
606         }
607 
608         /**
609          * Returns whether this transition is backed by a real system transition or not.
610          */
isSyntheticTransition()611         boolean isSyntheticTransition() {
612             return mTransition == SYNTHETIC_TRANSITION;
613         }
614 
615         /**
616          * Called when a synthetic transition is canceled.
617          */
cancelSyntheticTransition(String reason)618         boolean cancelSyntheticTransition(String reason) {
619             if (!isSyntheticTransition()) {
620                 return false;
621             }
622 
623             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
624                     "[%d] RecentsController.cancelSyntheticTransition: reason=%s",
625                     mInstanceId, reason);
626             try {
627                 // TODO(b/366021931): Notify the correct tasks once we build actual targets, and
628                 //                    clean up leashes accordingly
629                 mListener.onAnimationCanceled(new int[0], new TaskSnapshot[0]);
630             } catch (RemoteException e) {
631                 Slog.e(TAG, "Error canceling previous recents animation", e);
632             }
633             cleanUp();
634             return true;
635         }
636 
637         /**
638          * Called when a synthetic transition is finished.
639          * @return
640          */
finishSyntheticTransition(IResultReceiver runnerFinishCb, String reason)641         boolean finishSyntheticTransition(IResultReceiver runnerFinishCb, String reason) {
642             if (!isSyntheticTransition()) {
643                 return false;
644             }
645 
646             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
647                     "[%d] RecentsController.finishSyntheticTransition: reason=%s", mInstanceId,
648                     reason);
649             if (runnerFinishCb != null) {
650                 try {
651                     ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
652                             "[%d] RecentsController.finishInner: calling finish callback",
653                             mInstanceId);
654                     runnerFinishCb.send(0, null);
655                 } catch (RemoteException e) {
656                     Slog.e(TAG, "Failed to report transition finished", e);
657                 }
658             }
659             // TODO(b/366021931): Clean up leashes accordingly
660             cleanUp();
661             return true;
662         }
663 
start(TransitionInfo info, SurfaceControl.Transaction t, SurfaceControl.Transaction finishT, Transitions.TransitionFinishCallback finishCB)664         boolean start(TransitionInfo info, SurfaceControl.Transaction t,
665                 SurfaceControl.Transaction finishT, Transitions.TransitionFinishCallback finishCB) {
666             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
667                     "[%d] RecentsController.start", mInstanceId);
668             if (mListener == null || mTransition == null) {
669                 Slog.e(TAG, "Missing listener or transition, hasListener=" + (mListener != null) +
670                         " hasTransition=" + (mTransition != null));
671                 cancel("No listener (" + (mListener == null)
672                         + ") or no transition (" + (mTransition == null) + ")");
673                 return false;
674             }
675             // First see if this is a valid recents transition.
676             boolean hasPausingTasks = false;
677             for (int i = 0; i < info.getChanges().size(); ++i) {
678                 final TransitionInfo.Change change = info.getChanges().get(i);
679                 if (TransitionUtil.isWallpaper(change)) continue;
680                 if (TransitionUtil.isClosingType(change.getMode())) {
681                     hasPausingTasks = true;
682                     continue;
683                 }
684                 final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
685                 if (taskInfo != null && taskInfo.topActivityType == ACTIVITY_TYPE_RECENTS) {
686                     mRecentsTask = taskInfo.token;
687                     mRecentsTaskId = taskInfo.taskId;
688                 } else if (taskInfo != null && taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
689                     mRecentsTask = taskInfo.token;
690                     mRecentsTaskId = taskInfo.taskId;
691                 }
692             }
693             if (mRecentsTask == null && !hasPausingTasks) {
694                 // Recents is already running apparently, so this is a no-op.
695                 Slog.e(TAG, "Tried to start recents while it is already running.");
696                 cancel("No recents task and no pausing tasks");
697                 return false;
698             }
699 
700             mInfo = info;
701             mFinishCB = finishCB;
702             mFinishTransaction = finishT;
703             mPausingTasks = new ArrayList<>();
704             mClosingTasks = new ArrayList<>();
705             mOpeningTasks = new ArrayList<>();
706             mLeashMap = new ArrayMap<>();
707             mKeyguardLocked = (info.getFlags() & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0;
708 
709             int closingSplitTaskId = INVALID_TASK_ID;
710             final ArrayList<RemoteAnimationTarget> apps = new ArrayList<>();
711             final ArrayList<RemoteAnimationTarget> wallpapers = new ArrayList<>();
712             TransitionUtil.LeafTaskFilter leafTaskFilter = new TransitionUtil.LeafTaskFilter();
713             // About layering: we divide up the "layer space" into 3 regions (each the size of
714             // the change count). This lets us categorize things into above/below/between
715             // while maintaining their relative ordering.
716             final int belowLayers = info.getChanges().size();
717             final int middleLayers = info.getChanges().size() * 2;
718             final int aboveLayers = info.getChanges().size() * 3;
719 
720             // Add a background color to each transition root in this transition.
721             if (mBackgroundColor != null) {
722                 info.getChanges().stream()
723                         .mapToInt((change) -> TransitionUtil.rootIndexFor(change, info))
724                         .distinct()
725                         .mapToObj((rootIndex) -> info.getRoot(rootIndex).getLeash())
726                         .forEach((root) -> createBackgroundSurface(t, root, middleLayers));
727             }
728 
729             for (int i = 0; i < info.getChanges().size(); ++i) {
730                 final TransitionInfo.Change change = info.getChanges().get(i);
731                 final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
732                 if (TransitionUtil.isWallpaper(change)) {
733                     final RemoteAnimationTarget target = TransitionUtil.newTarget(change,
734                             // wallpapers go into the "below" layer space
735                             belowLayers - i, info, t, mLeashMap);
736                     wallpapers.add(target);
737                     // Make all the wallpapers opaque since we want them visible from the start
738                     t.setAlpha(target.leash, 1);
739                 } else if (leafTaskFilter.test(change)) {
740                     // start by putting everything into the "below" layer space.
741                     final RemoteAnimationTarget target = TransitionUtil.newTarget(change,
742                             belowLayers - i, info, t, mLeashMap);
743                     apps.add(target);
744                     if (TransitionUtil.isClosingType(change.getMode())) {
745                         mPausingTasks.add(new TaskState(change, target.leash));
746                         closingSplitTaskId = change.getTaskInfo().taskId;
747                         if (taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
748                             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
749                                     "  adding pausing leaf home taskId=%d", taskInfo.taskId);
750                             // This can only happen if we have a separate recents/home (3p launcher)
751                             mPausingSeparateHome = true;
752                         } else {
753                             final int layer = aboveLayers - i;
754                             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
755                                     "  adding pausing leaf taskId=%d at layer=%d",
756                                     taskInfo.taskId, layer);
757                             // raise closing (pausing) task to "above" layer so it isn't covered
758                             t.setLayer(target.leash, layer);
759                         }
760                         if (taskInfo.pictureInPictureParams != null
761                                 && taskInfo.pictureInPictureParams.isAutoEnterEnabled()) {
762                             mPipTask = taskInfo.token;
763                         }
764                     } else if (taskInfo != null
765                             && taskInfo.topActivityType == ACTIVITY_TYPE_RECENTS) {
766                         final int layer = middleLayers - i;
767                         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
768                                 "  setting recents activity layer=%d", layer);
769                         // There's a 3p launcher, so make sure recents goes above that, but under
770                         // the pausing apps.
771                         t.setLayer(target.leash, layer);
772                     } else if (taskInfo != null && taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
773                         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
774                                 "  not handling home taskId=%d", taskInfo.taskId);
775                         // do nothing
776                     } else if (TransitionUtil.isOpeningType(change.getMode())) {
777                         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
778                                 "  adding opening leaf taskId=%d", taskInfo.taskId);
779                         mOpeningTasks.add(new TaskState(change, target.leash));
780                     }
781                 } else if (taskInfo != null && TransitionInfo.isIndependent(change, info)) {
782                     // Root tasks
783                     if (TransitionUtil.isClosingType(change.getMode())) {
784                         final int layer = aboveLayers - i;
785                         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
786                                 "  adding pausing taskId=%d at layer=%d", taskInfo.taskId, layer);
787                         // raise closing (pausing) task to "above" layer so it isn't covered
788                         t.setLayer(change.getLeash(), layer);
789                         mPausingTasks.add(new TaskState(change, null /* leash */));
790                     } else if (TransitionUtil.isOpeningType(change.getMode())) {
791                         final int layer = belowLayers - i;
792                         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
793                                 "  adding opening taskId=%d at layer=%d", taskInfo.taskId, layer);
794                         // Put into the "below" layer space.
795                         t.setLayer(change.getLeash(), layer);
796                         mOpeningTasks.add(new TaskState(change, null /* leash */));
797                     } else {
798                         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
799                                 "  unhandled root taskId=%d", taskInfo.taskId);
800                     }
801                 } else if (TransitionUtil.isDividerBar(change)
802                         || TransitionUtil.isDimLayer(change)) {
803                     final RemoteAnimationTarget target = TransitionUtil.newTarget(change,
804                             belowLayers - i, info, t, mLeashMap);
805                     // Add this as a app and we will separate them on launcher side by window type.
806                     apps.add(target);
807                 } else {
808                     ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
809                             "  unhandled change taskId=%d",
810                             taskInfo != null ? taskInfo.taskId : -1);
811                 }
812             }
813             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
814                     "Applying transaction=%d", t.getId());
815             t.apply();
816 
817             mTakeoverHandler = mTransitions.getHandlerForTakeover(mTransition, info);
818 
819             Bundle b = new Bundle(2 /*capacity*/);
820             b.putParcelable(KEY_EXTRA_SPLIT_BOUNDS,
821                     mRecentTasksController.getSplitBoundsForTaskId(closingSplitTaskId));
822             b.putBoolean(KEY_EXTRA_SHELL_CAN_HAND_OFF_ANIMATION, mTakeoverHandler != null);
823             try {
824                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
825                         "[%d] RecentsController.start: calling onAnimationStart with %d apps",
826                         mInstanceId, apps.size());
827                 mListener.onAnimationStart(this,
828                         apps.toArray(new RemoteAnimationTarget[apps.size()]),
829                         wallpapers.toArray(new RemoteAnimationTarget[wallpapers.size()]),
830                         new Rect(0, 0, 0, 0), new Rect(), b, info);
831                 for (int i = 0; i < mStateListeners.size(); i++) {
832                     mStateListeners.get(i).onTransitionStateChanged(TRANSITION_STATE_ANIMATING);
833                 }
834             } catch (RemoteException e) {
835                 Slog.e(TAG, "Error starting recents animation", e);
836                 cancel("onAnimationStart() failed");
837             }
838             return true;
839         }
840 
841         @Override
handOffAnimation( RemoteAnimationTarget[] targets, WindowAnimationState[] states)842         public void handOffAnimation(
843                 RemoteAnimationTarget[] targets, WindowAnimationState[] states) {
844             mExecutor.execute(() -> {
845                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
846                         "[%d] RecentsController.handOffAnimation", mInstanceId);
847 
848                 if (mTakeoverHandler == null) {
849                     Slog.e(TAG, "Tried to hand off an animation without a valid takeover "
850                             + "handler.");
851                     return;
852                 }
853 
854                 if (targets.length != states.length) {
855                     Slog.e(TAG, "Tried to hand off an animation, but the number of targets "
856                             + "(" + targets.length + ") doesn't match the number of states "
857                             + "(" + states.length + ")");
858                     return;
859                 }
860 
861                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
862                         "[%d] RecentsController.handOffAnimation: got %d states for %d "
863                                 + "changes", mInstanceId, states.length, mInfo.getChanges().size());
864                 WindowAnimationState[] updatedStates =
865                         new WindowAnimationState[mInfo.getChanges().size()];
866 
867                 // Ensure that the ordering of animation states is the same as that of  matching
868                 // changes in mInfo. prefixOrderIndex is set up in reverse order to that of the
869                 // changes, so that's what we use to get to the correct ordering.
870                 for (int i = 0; i < targets.length; i++) {
871                     RemoteAnimationTarget target = targets[i];
872                     updatedStates[updatedStates.length - target.prefixOrderIndex] = states[i];
873                 }
874 
875                 Transitions.TransitionFinishCallback finishCB = mFinishCB;
876                 // Reset the callback here, so any stray calls that aren't coming from the new
877                 // handler are ignored.
878                 mFinishCB = null;
879 
880                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
881                         "[%d] RecentsController.handOffAnimation: calling "
882                                 + "takeOverAnimation with %d states", mInstanceId,
883                         updatedStates.length);
884                 mTakeoverHandler.takeOverAnimation(
885                         mTransition, mInfo, new SurfaceControl.Transaction(),
886                         wct -> {
887                             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
888                                     "[%d] RecentsController.handOffAnimation: finish "
889                                             + "callback", mInstanceId);
890                             // Set the callback once again so we can finish correctly.
891                             mFinishCB = finishCB;
892                             finishInner(true /* toHome */, false /* userLeave */,
893                                     null /* finishCb */, "takeOverAnimation");
894                         }, updatedStates);
895             });
896         }
897 
898         /**
899          * Updates this controller when a new transition is requested mid-recents transition.
900          */
handleMidTransitionRequest(TransitionRequestInfo request)901         void handleMidTransitionRequest(TransitionRequestInfo request) {
902             if (request.getType() == TRANSIT_CHANGE && request.getDisplayChange() != null) {
903                 final TransitionRequestInfo.DisplayChange dispChange = request.getDisplayChange();
904                 if (dispChange.getStartRotation() != dispChange.getEndRotation()) {
905                     ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
906                             "[%d] RecentsController.prepareForMerge: "
907                                     + "Snapshot due to requested display change",
908                             mInstanceId);
909                     mPendingPauseSnapshotsForCancel = getSnapshotsForPausingTasks();
910                 }
911             }
912         }
913 
914         /**
915          * Note: because we use a book-end transition to finish the recents transition, we must
916          * either always merge the incoming transition, or always cancel the recents transition
917          * if we don't handle the incoming transition to ensure that the end transition is queued
918          * before any unhandled transitions.
919          */
920         @SuppressLint("NewApi")
merge(TransitionInfo info, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, Transitions.TransitionFinishCallback finishCallback)921         void merge(TransitionInfo info, SurfaceControl.Transaction startT,
922                 SurfaceControl.Transaction finishT,
923                 Transitions.TransitionFinishCallback finishCallback) {
924             if (mFinishCB == null) {
925                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
926                         "[%d] RecentsController.merge: skip, no finish callback",
927                         mInstanceId);
928                 // This was no-op'd (likely a repeated start) and we've already completed finish.
929                 return;
930             }
931 
932             if (Flags.enableRecentsBookendTransition()) {
933                 if (info.getType() == TRANSIT_END_RECENTS_TRANSITION) {
934                     // This is a pending finish, so merge the end transition to trigger completing
935                     // the cleanup of the recents transition
936                     ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
937                             "[%d] RecentsController.merge: TRANSIT_END_RECENTS_TRANSITION",
938                             mInstanceId);
939                     consumeMerge(info, startT, finishT, finishCallback);
940                     return;
941                 } else if (mPendingFinishTransition != null) {
942                     // This transition is interrupting a pending finish that was already sent, so
943                     // pre-empt the pending finish transition since the state has already changed
944                     // in the core
945                     ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
946                             "[%d] RecentsController.merge: Awaiting TRANSIT_END_RECENTS_TRANSITION",
947                             mInstanceId);
948                     onFinishInner(null /* wct */);
949                     return;
950                 }
951             }
952 
953             if (info.getType() == TRANSIT_SLEEP) {
954                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
955                         "[%d] RecentsController.merge: transit_sleep", mInstanceId);
956                 // A sleep event means we need to stop animations immediately, so cancel here.
957                 cancel("transit_sleep");
958                 return;
959             }
960             if (mKeyguardLocked || (info.getFlags() & KEYGUARD_VISIBILITY_TRANSIT_FLAGS) != 0) {
961                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
962                         "[%d] RecentsController.merge: keyguard is locked", mInstanceId);
963                 // We will not accept new changes if we are swiping over the keyguard.
964                 cancel(true /* toHome */, false /* withScreenshots */, "keyguard_locked");
965                 return;
966             }
967             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
968                     "[%d] RecentsController.merge", mInstanceId);
969             // Keep all tasks in one list because order matters.
970             ArrayList<TransitionInfo.Change> openingTasks = null;
971             IntArray openingTaskIsLeafs = null;
972             ArrayList<TransitionInfo.Change> closingTasks = null;
973             mOpeningSeparateHome = false;
974             TransitionInfo.Change recentsOpening = null;
975             boolean foundRecentsClosing = false;
976             boolean hasChangingApp = false;
977             final TransitionUtil.LeafTaskFilter leafTaskFilter =
978                     new TransitionUtil.LeafTaskFilter();
979             boolean hasTaskChange = false;
980             for (int i = 0; i < info.getChanges().size(); ++i) {
981                 final TransitionInfo.Change change = info.getChanges().get(i);
982                 final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
983                 if (taskInfo != null
984                         && taskInfo.configuration.windowConfiguration.isAlwaysOnTop()) {
985                     // Tasks that are always on top (e.g. bubbles), will handle their own transition
986                     // as they are on top of everything else. So cancel the merge here.
987                     cancel(false /* toHome */, false /* withScreenshots */,
988                             "task #" + taskInfo.taskId + " is always_on_top");
989                     return;
990                 }
991                 if (TransitionUtil.isClosingType(change.getMode())
992                         && taskInfo != null && taskInfo.lastParentTaskIdBeforePip > 0) {
993                     // Pinned task is closing as a side effect of the removal of its original Task,
994                     // such transition should be handled by PiP. So cancel the merge here.
995                     cancel(false /* toHome */, false /* withScreenshots */,
996                             "task #" + taskInfo.taskId + " is removed with its original parent");
997                     return;
998                 }
999                 final boolean isRootTask = taskInfo != null
1000                         && TransitionInfo.isIndependent(change, info);
1001                 final boolean isRecentsTask = mRecentsTask != null
1002                         && mRecentsTask.equals(change.getContainer());
1003                 hasTaskChange = hasTaskChange || isRootTask;
1004                 final boolean isLeafTask = leafTaskFilter.test(change);
1005                 if (TransitionUtil.isOpeningType(change.getMode())
1006                         || TransitionUtil.isOrderOnly(change)) {
1007                     if (isRecentsTask) {
1008                         recentsOpening = change;
1009                     } else if (isRootTask || isLeafTask) {
1010                         if (isLeafTask && taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
1011                             // This is usually a 3p launcher
1012                             mOpeningSeparateHome = true;
1013                         }
1014                         if (openingTasks == null) {
1015                             openingTasks = new ArrayList<>();
1016                             openingTaskIsLeafs = new IntArray();
1017                         }
1018                         openingTasks.add(change);
1019                         openingTaskIsLeafs.add(isLeafTask ? 1 : 0);
1020                     }
1021                 } else if (TransitionUtil.isClosingType(change.getMode())) {
1022                     if (isRecentsTask) {
1023                         foundRecentsClosing = true;
1024                     } else if (isRootTask || isLeafTask) {
1025                         if (closingTasks == null) {
1026                             closingTasks = new ArrayList<>();
1027                         }
1028                         closingTasks.add(change);
1029                     }
1030                 } else if (change.getMode() == TRANSIT_CHANGE) {
1031                     // Finish recents animation if the display is changed, so the default
1032                     // transition handler can play the animation such as rotation effect.
1033                     if (change.hasFlags(TransitionInfo.FLAG_IS_DISPLAY)
1034                             && info.getType() == TRANSIT_CHANGE) {
1035                         // This call to cancel will use the screenshots taken preemptively in
1036                         // handleMidTransitionRequest() prior to the display changing
1037                         cancel(mWillFinishToHome, true /* withScreenshots */, "display change");
1038                         return;
1039                     }
1040                     // Don't consider order-only & non-leaf changes as changing apps.
1041                     if (!TransitionUtil.isOrderOnly(change) && isLeafTask) {
1042                         hasChangingApp = true;
1043                         // Check if the changing app is moving to top and fullscreen. This handles
1044                         // the case where we moved from desktop to recents and launching a desktop
1045                         // task in fullscreen.
1046                         if ((change.getFlags() & FLAG_MOVED_TO_TOP) != 0
1047                                 && taskInfo != null
1048                                 && taskInfo.getWindowingMode()
1049                                 == WINDOWING_MODE_FULLSCREEN) {
1050                             if (openingTasks == null) {
1051                                 openingTasks = new ArrayList<>();
1052                                 openingTaskIsLeafs = new IntArray();
1053                             }
1054                             openingTasks.add(change);
1055                             openingTaskIsLeafs.add(1);
1056                         }
1057                     } else if (isLeafTask && taskInfo.topActivityType == ACTIVITY_TYPE_HOME
1058                             && !isRecentsTask ) {
1059                         // Unless it is a 3p launcher. This means that the 3p launcher was already
1060                         // visible (eg. the "pausing" task is translucent over the 3p launcher).
1061                         // Treat it as if we are "re-opening" the 3p launcher.
1062                         if (openingTasks == null) {
1063                             openingTasks = new ArrayList<>();
1064                             openingTaskIsLeafs = new IntArray();
1065                         }
1066                         openingTasks.add(change);
1067                         openingTaskIsLeafs.add(1);
1068                     }
1069                 }
1070             }
1071             if (hasChangingApp && foundRecentsClosing) {
1072                 // This happens when a visible app is expanding (usually PiP). In this case,
1073                 // that transition probably has a special-purpose animation, so finish recents
1074                 // now and let it do its animation (since recents is going to be occluded).
1075                 sendCancelWithSnapshots();
1076                 mExecutor.executeDelayed(
1077                         () -> finishInner(true /* toHome */, false /* userLeaveHint */,
1078                                 null /* finishCb */, "merge"), 0);
1079                 return;
1080             }
1081             if (recentsOpening != null) {
1082                 // the recents task re-appeared. This happens if the user gestures before the
1083                 // task-switch (NEW_TASK) animation finishes.
1084                 if (mState == STATE_NORMAL) {
1085                     Slog.e(TAG, "Returning to recents while recents is already idle.");
1086                 }
1087                 if (closingTasks == null || closingTasks.size() == 0) {
1088                     Slog.e(TAG, "Returning to recents without closing any opening tasks.");
1089                 }
1090                 // Setup may hide it initially since it doesn't know that overview was still active.
1091                 startT.show(recentsOpening.getLeash());
1092                 startT.setAlpha(recentsOpening.getLeash(), 1.f);
1093                 mState = STATE_NORMAL;
1094             }
1095             boolean didMergeThings = false;
1096             if (closingTasks != null) {
1097                 // Potentially cancelling a task-switch. Move the tasks back to mPausing if they
1098                 // are in mOpening.
1099                 for (int i = 0; i < closingTasks.size(); ++i) {
1100                     final TransitionInfo.Change change = closingTasks.get(i);
1101                     final int pausingIdx = TaskState.indexOf(mPausingTasks, change);
1102                     if (pausingIdx >= 0) {
1103                         // We are closing the pausing task, but it is still visible and can be
1104                         // restart by another transition prior to this transition finishing
1105                         final TaskState closingTask = mPausingTasks.remove(pausingIdx);
1106                         mClosingTasks.add(closingTask);
1107                         didMergeThings = true;
1108                         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
1109                                 "  closing pausing taskId=%d", change.getTaskInfo().taskId);
1110                         continue;
1111                     }
1112                     int openingIdx = TaskState.indexOf(mOpeningTasks, change);
1113                     if (openingIdx < 0) {
1114                         Slog.w(TAG, "Closing a task that wasn't opening, this may be split or"
1115                                 + " something unexpected: " + change.getTaskInfo().taskId);
1116                         continue;
1117                     }
1118                     final TaskState openingTask = mOpeningTasks.remove(openingIdx);
1119                     ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
1120                             "  pausing opening %staskId=%d", openingTask.isLeaf() ? "leaf " : "",
1121                             openingTask.mTaskInfo.taskId);
1122                     mPausingTasks.add(openingTask);
1123                     didMergeThings = true;
1124                 }
1125             }
1126             RemoteAnimationTarget[] appearedTargets = null;
1127             if (openingTasks != null && openingTasks.size() > 0) {
1128                 // Switching to some new tasks, add to mOpening and remove from mPausing. Also,
1129                 // enter NEW_TASK state since this will start the switch-to animation.
1130                 final int layer = mInfo.getChanges().size() * 3;
1131                 int openingLeafCount = 0;
1132                 for (int i = 0; i < openingTaskIsLeafs.size(); ++i) {
1133                     openingLeafCount += openingTaskIsLeafs.get(i);
1134                 }
1135                 if (openingLeafCount > 0) {
1136                     appearedTargets = new RemoteAnimationTarget[openingLeafCount];
1137                 }
1138                 boolean onlyOpeningPausedTasks = true;
1139                 int nextTargetIdx = 0;
1140                 for (int i = 0; i < openingTasks.size(); ++i) {
1141                     final TransitionInfo.Change change = openingTasks.get(i);
1142                     final boolean isLeaf = openingTaskIsLeafs.get(i) == 1;
1143                     final int closingIdx = TaskState.indexOf(mClosingTasks, change);
1144                     if (closingIdx >= 0) {
1145                         // Remove opening tasks from closing set
1146                         mClosingTasks.remove(closingIdx);
1147                     }
1148                     final int pausingIdx = TaskState.indexOf(mPausingTasks, change);
1149                     if (pausingIdx >= 0) {
1150                         // Something is showing/opening a previously-pausing app.
1151                         if (isLeaf) {
1152                             appearedTargets[nextTargetIdx++] = TransitionUtil.newTarget(
1153                                     change, layer, mPausingTasks.get(pausingIdx).mLeash);
1154                         }
1155                         final TaskState pausingTask = mPausingTasks.remove(pausingIdx);
1156                         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
1157                                 "  opening pausing %staskId=%d", isLeaf ? "leaf " : "",
1158                                 pausingTask.mTaskInfo.taskId);
1159                         mOpeningTasks.add(pausingTask);
1160                         // Setup hides opening tasks initially, so make it visible again (since we
1161                         // are already showing it).
1162                         startT.show(change.getLeash());
1163                         startT.setAlpha(change.getLeash(), 1.f);
1164                     } else if (isLeaf) {
1165                         // We are receiving new opening leaf tasks, so convert to onTasksAppeared.
1166                         final RemoteAnimationTarget target = TransitionUtil.newTarget(
1167                                 change, layer, info, startT, mLeashMap);
1168                         appearedTargets[nextTargetIdx++] = target;
1169                         // reparent into the original `mInfo` since that's where we are animating.
1170                         final TransitionInfo.Root root = TransitionUtil.getRootFor(change, mInfo);
1171                         final boolean wasClosing = closingIdx >= 0;
1172                         startT.reparent(target.leash, root.getLeash());
1173                         startT.setPosition(target.leash,
1174                                 change.getStartAbsBounds().left - root.getOffset().x,
1175                                 change.getStartAbsBounds().top - root.getOffset().y);
1176                         startT.setLayer(target.leash, layer);
1177                         if (wasClosing) {
1178                             // App was previously visible and is closing
1179                             startT.show(target.leash);
1180                             startT.setAlpha(target.leash, 1f);
1181                             // Also override the task alpha as it was set earlier when dispatching
1182                             // the transition and setting up the leash to hide the
1183                             startT.setAlpha(change.getLeash(), 1f);
1184                         } else {
1185                             // Hide the animation leash, let the listener show it
1186                             startT.hide(target.leash);
1187                         }
1188                         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
1189                                 "  opening new leaf taskId=%d wasClosing=%b",
1190                                 target.taskId, wasClosing);
1191                         mOpeningTasks.add(new TaskState(change, target.leash));
1192                         onlyOpeningPausedTasks = false;
1193                     } else {
1194                         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
1195                                 "  opening new taskId=%d", change.getTaskInfo().taskId);
1196                         startT.setLayer(change.getLeash(), layer);
1197                         // Setup hides opening tasks initially, so make it visible since recents
1198                         // is only animating the leafs.
1199                         startT.show(change.getLeash());
1200                         mOpeningTasks.add(new TaskState(change, null));
1201                         onlyOpeningPausedTasks = false;
1202                     }
1203                 }
1204                 didMergeThings = true;
1205                 if (!onlyOpeningPausedTasks) {
1206                     // If we are only opening paused leaf tasks, then we aren't actually quick
1207                     // switching or launching a new task from overview, and if Launcher requests to
1208                     // finish(toHome=false) as a response to the pausing tasks being opened again,
1209                     // we should allow that to be considered returningToApp
1210                     mState = STATE_NEW_TASK;
1211                 }
1212             }
1213             if (mPausingTasks.isEmpty()) {
1214                 // The pausing tasks may be removed by the incoming closing tasks.
1215                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
1216                         "[%d] RecentsController.merge: empty pausing tasks", mInstanceId);
1217             }
1218             if (!hasTaskChange) {
1219                 // Activity only transition, so consume the merge as it doesn't affect the rest of
1220                 // recents.
1221                 Slog.d(TAG, "Got an activity only transition during recents, so apply directly");
1222                 mergeActivityOnly(info, startT);
1223             } else if (!didMergeThings) {
1224                 // Didn't recognize anything in incoming transition so don't merge it.
1225                 Slog.w(TAG, "Don't know how to merge this transition, foundRecentsClosing="
1226                         + foundRecentsClosing + " recentsTaskId=" + mRecentsTaskId);
1227                 if (foundRecentsClosing || mRecentsTaskId < 0) {
1228                     mWillFinishToHome = false;
1229                     cancel(false /* toHome */, false /* withScreenshots */, "didn't merge");
1230                 }
1231                 return;
1232             }
1233 
1234             // At this point, we are accepting the merge.
1235             consumeMerge(info, startT, finishT, finishCallback);
1236 
1237             // Notify Launcher of the new opening tasks if necessary
1238             boolean passTransitionInfo = ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX.isTrue();
1239             if (appearedTargets != null) {
1240                 try {
1241                     ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
1242                             "[%d] RecentsController.merge: calling onTasksAppeared", mInstanceId);
1243                     mListener.onTasksAppeared(appearedTargets, passTransitionInfo ? info : null);
1244                 } catch (RemoteException e) {
1245                     Slog.e(TAG, "Error sending appeared tasks to recents animation", e);
1246                 }
1247             }
1248         }
1249 
1250         /**
1251          * Consumes the merge of the other given transition.
1252          */
consumeMerge(TransitionInfo info, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, Transitions.TransitionFinishCallback finishCallback)1253         private void consumeMerge(TransitionInfo info, SurfaceControl.Transaction startT,
1254                 SurfaceControl.Transaction finishT,
1255                 Transitions.TransitionFinishCallback finishCallback) {
1256             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
1257                     "[%d] RecentsController.merge: consuming merge",
1258                     mInstanceId);
1259 
1260             startT.apply();
1261             // Since we're accepting the merge, update the finish transaction so that changes via
1262             // that transaction will be applied on top of those of the merged transitions
1263             mFinishTransaction = finishT;
1264             boolean passTransitionInfo = ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX.isTrue();
1265             if (!passTransitionInfo) {
1266                 // not using the incoming anim-only surfaces
1267                 info.releaseAnimSurfaces();
1268             }
1269             finishCallback.onTransitionFinished(null /* wct */);
1270         }
1271 
1272         /** For now, just set-up a jump-cut to the new activity. */
mergeActivityOnly(TransitionInfo info, SurfaceControl.Transaction t)1273         private void mergeActivityOnly(TransitionInfo info, SurfaceControl.Transaction t) {
1274             for (int i = 0; i < info.getChanges().size(); ++i) {
1275                 final TransitionInfo.Change change = info.getChanges().get(i);
1276                 if (TransitionUtil.isOpeningType(change.getMode())) {
1277                     t.show(change.getLeash());
1278                     t.setAlpha(change.getLeash(), 1.f);
1279                 } else if (TransitionUtil.isClosingType(change.getMode())) {
1280                     t.hide(change.getLeash());
1281                 }
1282             }
1283         }
1284 
1285         @Override
setInputConsumerEnabled(boolean enabled)1286         public void setInputConsumerEnabled(boolean enabled) {
1287             mExecutor.execute(() -> {
1288                 if (mFinishCB == null || !enabled) {
1289                     ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
1290                             "RecentsController.setInputConsumerEnabled: skip, cb?=%b enabled?=%b",
1291                             mFinishCB != null, enabled);
1292                     return;
1293                 }
1294                 final int displayId = mInfo.getRootCount() > 0 ? mInfo.getRoot(0).getDisplayId()
1295                         : DEFAULT_DISPLAY;
1296                 // transient launches don't receive focus automatically. Since we are taking over
1297                 // the gesture now, take focus explicitly.
1298                 // This also moves recents back to top if the user gestured before a switch
1299                 // animation finished.
1300                 try {
1301                     ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
1302                             "[%d] RecentsController.setInputConsumerEnabled: set focus to recents",
1303                             mInstanceId);
1304                     ActivityTaskManager.getService().focusTopTask(displayId);
1305                 } catch (RemoteException e) {
1306                     Slog.e(TAG, "Failed to set focused task", e);
1307                 }
1308             });
1309         }
1310 
1311         @Override
setFinishTaskTransaction(int taskId, PictureInPictureSurfaceTransaction finishTransaction, SurfaceControl overlay)1312         public void setFinishTaskTransaction(int taskId,
1313                 PictureInPictureSurfaceTransaction finishTransaction, SurfaceControl overlay) {
1314             mExecutor.execute(() -> {
1315                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
1316                         "[%d] RecentsController.setFinishTaskTransaction: taskId=%d,"
1317                                 + " [mFinishCB is non-null]=%b",
1318                         mInstanceId, taskId, mFinishCB != null);
1319                 if (mFinishCB == null) return;
1320                 mPipTransaction = finishTransaction;
1321                 mPipTaskId = taskId;
1322             });
1323         }
1324 
1325         @Override
1326         @SuppressLint("NewApi")
finish(boolean toHome, boolean sendUserLeaveHint, IResultReceiver finishCb)1327         public void finish(boolean toHome, boolean sendUserLeaveHint, IResultReceiver finishCb) {
1328             mExecutor.execute(() -> finishInner(toHome, sendUserLeaveHint, finishCb,
1329                     "requested"));
1330         }
1331 
1332         /**
1333          * @param runnerFinishCb The remote finish callback to run after finish is complete, this is
1334          *                       not the same as mFinishCb which reports the transition is finished
1335          *                       to WM.
1336          */
finishInner(boolean toHome, boolean sendUserLeaveHint, IResultReceiver runnerFinishCb, String reason)1337         private void finishInner(boolean toHome, boolean sendUserLeaveHint,
1338                 IResultReceiver runnerFinishCb, String reason) {
1339             if (finishSyntheticTransition(runnerFinishCb, reason)) {
1340                 return;
1341             }
1342 
1343             if (mFinishCB == null || (Flags.enableRecentsBookendTransition()
1344                     && mPendingFinishTransition != null)) {
1345                 Slog.e(TAG, "Duplicate call to finish");
1346                 if (runnerFinishCb != null) {
1347                     try {
1348                         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
1349                                 "[%d] RecentsController.finishInner: calling finish callback",
1350                                 mInstanceId);
1351                         runnerFinishCb.send(0, null);
1352                     } catch (RemoteException e) {
1353                         Slog.e(TAG, "Failed to report transition finished", e);
1354                     }
1355                 }
1356                 return;
1357             }
1358 
1359             boolean returningToApp = !toHome
1360                     && !mWillFinishToHome
1361                     && mPausingTasks != null
1362                     && mState == STATE_NORMAL;
1363             if (!Flags.enableRecentsBookendTransition()) {
1364                 // This is only necessary when the recents transition is finished using a finishWCT,
1365                 // otherwise a new transition will notify the relevant observers
1366                 if (returningToApp && allAppsAreTranslucent(mPausingTasks)) {
1367                     mHomeTransitionObserver.notifyHomeVisibilityChanged(true);
1368                 } else if (!toHome && mState == STATE_NEW_TASK
1369                         && allAppsAreTranslucent(mOpeningTasks)) {
1370                     // We are opening a translucent app. Launcher is still visible so we do nothing.
1371                 } else if (!toHome) {
1372                     // For some transitions, we may have notified home activity that it became
1373                     // visible. We need to notify the observer that we are no longer going home.
1374                     mHomeTransitionObserver.notifyHomeVisibilityChanged(false);
1375                 }
1376             }
1377 
1378             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
1379                     "[%d] RecentsController.finishInner: toHome=%b userLeave=%b "
1380                             + "willFinishToHome=%b state=%d hasPausingTasks=%b reason=%s",
1381                     mInstanceId, toHome, sendUserLeaveHint, mWillFinishToHome, mState,
1382                     mPausingTasks != null, reason);
1383 
1384             final SurfaceControl.Transaction t = mFinishTransaction;
1385             final WindowContainerTransaction wct = new WindowContainerTransaction();
1386 
1387             // The following code must set this if it is changing anything in core that might affect
1388             // transitions as a part of finishing the recents transition
1389             boolean requiresBookendTransition = false;
1390 
1391             if (mKeyguardLocked && mRecentsTask != null) {
1392                 if (toHome) wct.reorder(mRecentsTask, true /* toTop */);
1393                 else wct.restoreTransientOrder(mRecentsTask);
1394                 // We are manipulating the window hierarchy, which should only be done with the
1395                 // bookend transition
1396                 requiresBookendTransition = true;
1397             }
1398             if (returningToApp) {
1399                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "  returning to app");
1400                 // The gesture is returning to the pausing-task(s) rather than continuing with
1401                 // recents, so end the transition by moving the app back to the top (and also
1402                 // re-showing it's task).
1403                 for (int i = mPausingTasks.size() - 1; i >= 0; --i) {
1404                     // reverse order so that index 0 ends up on top
1405                     wct.reorder(mPausingTasks.get(i).mToken, true /* onTop */);
1406                     t.show(mPausingTasks.get(i).mTaskSurface);
1407                 }
1408                 setCornerRadiusForFreeformTasks(
1409                         mRecentTasksController.getContext(), t, mPausingTasks);
1410                 if (!mKeyguardLocked && mRecentsTask != null) {
1411                     wct.restoreTransientOrder(mRecentsTask);
1412                 }
1413                 // We are manipulating the window hierarchy, which should only be done with the
1414                 // bookend transition
1415                 requiresBookendTransition = true;
1416             } else if (toHome && mOpeningSeparateHome && mPausingTasks != null) {
1417                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "  3p launching home");
1418                 // Special situation where 3p launcher was changed during recents (this happens
1419                 // during tapltests...). Here we get both "return to home" AND "home opening".
1420                 // This is basically going home, but we have to restore the recents and home order.
1421                 for (int i = 0; i < mOpeningTasks.size(); ++i) {
1422                     final TaskState state = mOpeningTasks.get(i);
1423                     if (state.mTaskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
1424                         // Make sure it is on top.
1425                         wct.reorder(state.mToken, true /* onTop */);
1426                     }
1427                     t.show(state.mTaskSurface);
1428                 }
1429                 for (int i = mPausingTasks.size() - 1; i >= 0; --i) {
1430                     t.hide(mPausingTasks.get(i).mTaskSurface);
1431                 }
1432                 if (!mKeyguardLocked && mRecentsTask != null) {
1433                     wct.restoreTransientOrder(mRecentsTask);
1434                 }
1435                 // We are manipulating the window hierarchy, which should only be done with the
1436                 // bookend transition
1437                 requiresBookendTransition = true;
1438             } else {
1439                 if (mPausingSeparateHome) {
1440                     if (mOpeningTasks.isEmpty()) {
1441                         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
1442                                 "  recents occluded 3p home");
1443                     } else {
1444                         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
1445                                 "  switch task by recents on 3p home");
1446                     }
1447                 }
1448                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "  normal finish");
1449                 // The general case: committing to recents, going home, or switching tasks.
1450                 for (int i = 0; i < mOpeningTasks.size(); ++i) {
1451                     t.show(mOpeningTasks.get(i).mTaskSurface);
1452                 }
1453                 setCornerRadiusForFreeformTasks(
1454                         mRecentTasksController.getContext(), t, mOpeningTasks);
1455                 for (int i = 0; i < mPausingTasks.size(); ++i) {
1456                     cleanUpPausingOrClosingTask(mPausingTasks.get(i), wct, t, sendUserLeaveHint);
1457                 }
1458                 for (int i = 0; i < mClosingTasks.size(); ++i) {
1459                     cleanUpPausingOrClosingTask(mClosingTasks.get(i), wct, t, sendUserLeaveHint);
1460                 }
1461 
1462                 if (mPipTransaction != null && sendUserLeaveHint) {
1463                     SurfaceControl pipLeash = null;
1464                     TransitionInfo.Change pipChange = null;
1465                     if (mPipTask != null) {
1466                         pipChange = mInfo.getChange(mPipTask);
1467                         pipLeash = pipChange.getLeash();
1468                     } else if (mPipTaskId != -1) {
1469                         // find a task with taskId from #setFinishTaskTransaction()
1470                         for (TransitionInfo.Change change : mInfo.getChanges()) {
1471                             if (change.getTaskInfo() != null
1472                                     && change.getTaskInfo().taskId == mPipTaskId) {
1473                                 pipChange = change;
1474                                 pipLeash = change.getLeash();
1475                                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
1476                                         "RecentsController.finishInner:"
1477                                                 + " found a change with taskId=%d", mPipTaskId);
1478                             }
1479                         }
1480                     }
1481                     if (pipLeash == null) {
1482                         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
1483                                 "RecentsController.finishInner: no valid PiP leash;"
1484                                         + "mPipTransaction=%s, mPipTask=%s, mPipTaskId=%d",
1485                                 mPipTransaction, mPipTask, mPipTaskId);
1486                     } else {
1487                         t.show(pipLeash);
1488                         PictureInPictureSurfaceTransaction.apply(mPipTransaction, pipLeash, t);
1489                         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
1490                                 "RecentsController.finishInner: PiP transaction %s merged",
1491                                 mPipTransaction);
1492                         if (PipUtils.isPip2ExperimentEnabled()) {
1493                             // If this path is triggered, we are in auto-enter PiP flow in gesture
1494                             // navigation mode, which means "Recents" transition should be followed
1495                             // by a TRANSIT_PIP. Hence, we take the WCT was about to be sent
1496                             // to Core to be applied during finishTransition(), we modify it to
1497                             // factor in PiP changes, and we send it as a direct startWCT for
1498                             // a new TRANSIT_PIP type transition. Recents still sends
1499                             // finishTransition() to update visibilities, but with finishWCT=null.
1500                             TransitionRequestInfo requestInfo = new TransitionRequestInfo(
1501                                     TRANSIT_PIP, null /* triggerTask */, pipChange.getTaskInfo(),
1502                                     null /* remote */, null /* displayChange */, 0 /* flags */);
1503                             // Use mTransition IBinder token temporarily just to get PipTransition
1504                             // to return from its handleRequest(). The actual TRANSIT_PIP will have
1505                             // anew token once it arrives into PipTransition#startAnimation().
1506                             Pair<Transitions.TransitionHandler, WindowContainerTransaction>
1507                                     requestRes = mTransitions.dispatchRequest(mTransition,
1508                                             requestInfo, null /* skip */);
1509                             wct.merge(requestRes.second, true);
1510                             mTransitions.startTransition(TRANSIT_PIP, wct, null /* handler */);
1511                             // We need to clear the WCT to send finishWCT=null for Recents.
1512                             wct.clear();
1513 
1514                             if (Flags.enableRecentsBookendTransition()) {
1515                                 // Notify the mixers of the pending finish
1516                                 for (int i = 0; i < mMixers.size(); ++i) {
1517                                     mMixers.get(i).handleFinishRecents(returningToApp, wct, t);
1518                                 }
1519 
1520                                 // In this case, we've already started the PIP transition, so we can
1521                                 // clean up immediately
1522                                 mPendingRunnerFinishCb = runnerFinishCb;
1523                                 onFinishInner(null);
1524                                 return;
1525                             }
1526                         }
1527                     }
1528                 }
1529             }
1530 
1531             // Notify the mixers of the pending finish
1532             for (int i = 0; i < mMixers.size(); ++i) {
1533                 mMixers.get(i).handleFinishRecents(returningToApp, wct, t);
1534             }
1535 
1536             if (Flags.enableRecentsBookendTransition()) {
1537                 if (!wct.isEmpty()) {
1538                     if (requiresBookendTransition) {
1539                         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
1540                                 "[%d] RecentsController.finishInner: "
1541                                         + "Queuing TRANSIT_END_RECENTS_TRANSITION", mInstanceId);
1542                         mPendingRunnerFinishCb = runnerFinishCb;
1543                         mPendingFinishTransition = mTransitions.startTransition(
1544                                 TRANSIT_END_RECENTS_TRANSITION, wct,
1545                                 new PendingFinishTransitionHandler());
1546                     } else {
1547                         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
1548                                 "[%d] RecentsController.finishInner: Non-transition affecting wct",
1549                                 mInstanceId);
1550                         mPendingRunnerFinishCb = runnerFinishCb;
1551                         onFinishInner(wct);
1552                     }
1553                 } else {
1554                     // If there's no work to do, just go ahead and clean up
1555                     mPendingRunnerFinishCb = runnerFinishCb;
1556                     onFinishInner(null /* wct */);
1557                 }
1558             } else {
1559                 mPendingRunnerFinishCb = runnerFinishCb;
1560                 onFinishInner(wct);
1561             }
1562         }
1563 
1564         /**
1565          * Runs the actual logic to finish the recents transition.
1566          */
onFinishInner(@ullable WindowContainerTransaction wct)1567         private void onFinishInner(@Nullable WindowContainerTransaction wct) {
1568             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
1569                     "[%d] RecentsController.finishInner: Completing finish", mInstanceId);
1570             final Transitions.TransitionFinishCallback finishCb = mFinishCB;
1571             final IResultReceiver runnerFinishCb = mPendingRunnerFinishCb;
1572 
1573             cleanUp();
1574             finishCb.onTransitionFinished(wct);
1575             if (runnerFinishCb != null) {
1576                 try {
1577                     ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
1578                             "[%d] RecentsController.finishInner: calling finish callback",
1579                             mInstanceId);
1580                     runnerFinishCb.send(0, null);
1581                 } catch (RemoteException e) {
1582                     Slog.e(TAG, "Failed to report transition finished", e);
1583                 }
1584             }
1585         }
1586 
setCornerRadiusForFreeformTasks( Context context, SurfaceControl.Transaction t, ArrayList<TaskState> tasks)1587         private static void setCornerRadiusForFreeformTasks(
1588                 Context context,
1589                 SurfaceControl.Transaction t,
1590                 ArrayList<TaskState> tasks) {
1591             if (!ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX.isTrue()) {
1592                 return;
1593             }
1594             int cornerRadius = getCornerRadius(context);
1595             for (int i = 0; i < tasks.size(); ++i) {
1596                 TaskState task = tasks.get(i);
1597                 if (task.mTaskInfo != null && task.mTaskInfo.isFreeform()) {
1598                     t.setCornerRadius(task.mTaskSurface, cornerRadius);
1599                 }
1600             }
1601         }
1602 
getCornerRadius(Context context)1603         private static int getCornerRadius(Context context) {
1604             return context.getResources().getDimensionPixelSize(
1605                     R.dimen.desktop_windowing_freeform_rounded_corner_radius);
1606         }
1607 
allAppsAreTranslucent(ArrayList<TaskState> tasks)1608         private boolean allAppsAreTranslucent(ArrayList<TaskState> tasks) {
1609             if (tasks == null) {
1610                 return false;
1611             }
1612             for (int i = tasks.size() - 1; i >= 0; --i) {
1613                 if (!tasks.get(i).mIsTranslucent) {
1614                     return false;
1615                 }
1616             }
1617             return true;
1618         }
1619 
createBackgroundSurface(SurfaceControl.Transaction transaction, SurfaceControl parent, int layer)1620         private void createBackgroundSurface(SurfaceControl.Transaction transaction,
1621                 SurfaceControl parent, int layer) {
1622             if (mBackgroundColor == null) {
1623                 return;
1624             }
1625             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
1626                     "  adding background color to layer=%d", layer);
1627             final SurfaceControl background = new SurfaceControl.Builder()
1628                     .setName("recents_background")
1629                     .setColorLayer()
1630                     .setOpaque(true)
1631                     .setParent(parent)
1632                     .build();
1633             transaction.setColor(background, colorToFloatArray(mBackgroundColor));
1634             transaction.setLayer(background, layer);
1635             transaction.setAlpha(background, 1F);
1636             transaction.show(background);
1637         }
1638 
colorToFloatArray(@onNull Color color)1639         private static float[] colorToFloatArray(@NonNull Color color) {
1640             return new float[]{color.red(), color.green(), color.blue()};
1641         }
1642 
cleanUpPausingOrClosingTask(TaskState task, WindowContainerTransaction wct, SurfaceControl.Transaction finishTransaction, boolean sendUserLeaveHint)1643         private void cleanUpPausingOrClosingTask(TaskState task, WindowContainerTransaction wct,
1644                 SurfaceControl.Transaction finishTransaction, boolean sendUserLeaveHint) {
1645             if (!sendUserLeaveHint && task.isLeaf()) {
1646                 // This means recents is not *actually* finishing, so of course we gotta
1647                 // do special stuff in WMCore to accommodate.
1648                 wct.setDoNotPip(task.mToken);
1649             }
1650             // Since we will reparent out of the leashes, pre-emptively hide the child
1651             // surface to match the leash. Otherwise, there will be a flicker before the
1652             // visibility gets committed in Core when using split-screen (in splitscreen,
1653             // the leaf-tasks are not "independent" so aren't hidden by normal setup).
1654             finishTransaction.hide(task.mTaskSurface);
1655         }
1656 
1657         @Override
setWillFinishToHome(boolean willFinishToHome)1658         public void setWillFinishToHome(boolean willFinishToHome) {
1659             mExecutor.execute(() -> {
1660                 mWillFinishToHome = willFinishToHome;
1661             });
1662         }
1663 
1664         /**
1665          * @see IRecentsAnimationController#detachNavigationBarFromApp
1666          */
1667         @Override
detachNavigationBarFromApp(boolean moveHomeToTop)1668         public void detachNavigationBarFromApp(boolean moveHomeToTop) {
1669             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
1670                     "[%d] RecentsController.detachNavigationBarFromApp", mInstanceId);
1671             mExecutor.execute(() -> {
1672                 if (mTransition == null) return;
1673                 try {
1674                     ActivityTaskManager.getService().detachNavigationBarFromApp(mTransition);
1675                 } catch (RemoteException e) {
1676                     Slog.e(TAG, "Failed to detach the navigation bar from app", e);
1677                 }
1678             });
1679         }
1680 
1681         /**
1682          * A temporary transition handler used with the pending finish transition, which runs the
1683          * cleanup/finish logic once the pending transition is merged/handled.
1684          * This is only initialized if Flags.enableRecentsBookendTransition() is enabled.
1685          */
1686         private class PendingFinishTransitionHandler implements Transitions.TransitionHandler {
1687             @Override
startAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback)1688             public boolean startAnimation(@NonNull IBinder transition,
1689                     @NonNull TransitionInfo info,
1690                     @NonNull SurfaceControl.Transaction startTransaction,
1691                     @NonNull SurfaceControl.Transaction finishTransaction,
1692                     @NonNull Transitions.TransitionFinishCallback finishCallback) {
1693                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
1694                         "[%d] PendingFinishTransitionHandler.startAnimation: "
1695                                 + "Started pending finish transition", mInstanceId);
1696                 return false;
1697             }
1698 
1699             @Nullable
1700             @Override
handleRequest(@onNull IBinder transition, @NonNull TransitionRequestInfo request)1701             public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
1702                     @NonNull TransitionRequestInfo request) {
1703                 return null;
1704             }
1705 
1706             @Override
onTransitionConsumed(@onNull IBinder transition, boolean aborted, @Nullable SurfaceControl.Transaction finishTransaction)1707             public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
1708                     @Nullable SurfaceControl.Transaction finishTransaction) {
1709                 if (mPendingFinishTransition == null) {
1710                     // The cleanup was pre-empted by an earlier transition, nothing there is nothing
1711                     // to do here
1712                     return;
1713                 }
1714                 // Once we have merged (or not if the WCT didn't result in any changes), then we can
1715                 // run the pending finish logic
1716                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
1717                         "[%d] PendingFinishTransitionHandler.onTransitionConsumed: "
1718                                 + "Consumed pending finish transition", mInstanceId);
1719                 onFinishInner(null /* wct */);
1720             }
1721         };
1722     };
1723 
1724     /** Utility class to track the state of a task as-seen by recents. */
1725     private static class TaskState {
1726         WindowContainerToken mToken;
1727         ActivityManager.RunningTaskInfo mTaskInfo;
1728 
1729         /** The surface/leash of the task provided by Core. */
1730         SurfaceControl mTaskSurface;
1731 
1732         /** True when the task is translucent.  */
1733         final boolean mIsTranslucent;
1734 
1735         /** The (local) animation-leash created for this task. Only non-null for leafs. */
1736         @Nullable
1737         SurfaceControl mLeash;
1738 
TaskState(TransitionInfo.Change change, SurfaceControl leash)1739         TaskState(TransitionInfo.Change change, SurfaceControl leash) {
1740             mToken = change.getContainer();
1741             mTaskInfo = change.getTaskInfo();
1742             mTaskSurface = change.getLeash();
1743             mIsTranslucent = (change.getFlags() & FLAG_TRANSLUCENT) != 0;
1744             mLeash = leash;
1745         }
1746 
indexOf(ArrayList<TaskState> list, TransitionInfo.Change change)1747         static int indexOf(ArrayList<TaskState> list, TransitionInfo.Change change) {
1748             for (int i = list.size() - 1; i >= 0; --i) {
1749                 if (list.get(i).mToken.equals(change.getContainer())) {
1750                     return i;
1751                 }
1752             }
1753             return -1;
1754         }
1755 
isLeaf()1756         boolean isLeaf() {
1757             return mLeash != null;
1758         }
1759 
toString()1760         public String toString() {
1761             return "" + mToken + " : " + mLeash;
1762         }
1763     }
1764 
1765     /**
1766      * An interface for a mixed handler to receive information about recents requests (since these
1767      * come into this handler directly vs from WMCore request).
1768      */
1769     public interface RecentsMixedHandler extends Transitions.TransitionHandler {
1770         /**
1771          * Called when a recents request comes in. If the handler wants to "accept" the transition,
1772          * it should return a Consumer accepting the IBinder for the transition. If not, it should
1773          * return `null`.
1774          *
1775          * If a mixed-handler accepts this recents, it will be the de-facto handler for this
1776          * transition and is required to call the associated {@link #startAnimation},
1777          * {@link #mergeAnimation}, and {@link #onTransitionConsumed} methods.
1778          */
1779         @Nullable
handleRecentsRequest()1780         Consumer<IBinder> handleRecentsRequest();
1781 
1782         /**
1783          * Called when a recents transition has finished, with a WCT and SurfaceControl Transaction
1784          * that can be used to add to any changes needed to restore the state.
1785          */
handleFinishRecents(boolean returnToApp, @NonNull WindowContainerTransaction finishWct, @NonNull SurfaceControl.Transaction finishT)1786         void handleFinishRecents(boolean returnToApp, @NonNull WindowContainerTransaction finishWct,
1787                 @NonNull SurfaceControl.Transaction finishT);
1788     }
1789 }
1790