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.view.WindowManager.KEYGUARD_VISIBILITY_TRANSIT_FLAGS;
23 import static android.view.WindowManager.TRANSIT_CHANGE;
24 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
25 import static android.view.WindowManager.TRANSIT_PIP;
26 import static android.view.WindowManager.TRANSIT_SLEEP;
27 import static android.view.WindowManager.TRANSIT_TO_FRONT;
28 import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
29 
30 import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_CAN_HAND_OFF_ANIMATION;
31 import static com.android.wm.shell.util.SplitBounds.KEY_EXTRA_SPLIT_BOUNDS;
32 
33 import android.annotation.Nullable;
34 import android.annotation.SuppressLint;
35 import android.app.ActivityManager;
36 import android.app.ActivityTaskManager;
37 import android.app.IApplicationThread;
38 import android.app.PendingIntent;
39 import android.content.Intent;
40 import android.graphics.Color;
41 import android.graphics.Rect;
42 import android.os.Bundle;
43 import android.os.IBinder;
44 import android.os.RemoteException;
45 import android.util.ArrayMap;
46 import android.util.IntArray;
47 import android.util.Pair;
48 import android.util.Slog;
49 import android.view.Display;
50 import android.view.IRecentsAnimationController;
51 import android.view.IRecentsAnimationRunner;
52 import android.view.RemoteAnimationTarget;
53 import android.view.SurfaceControl;
54 import android.window.PictureInPictureSurfaceTransaction;
55 import android.window.TaskSnapshot;
56 import android.window.TransitionInfo;
57 import android.window.TransitionRequestInfo;
58 import android.window.WindowAnimationState;
59 import android.window.WindowContainerToken;
60 import android.window.WindowContainerTransaction;
61 
62 import androidx.annotation.NonNull;
63 
64 import com.android.internal.annotations.VisibleForTesting;
65 import com.android.internal.os.IResultReceiver;
66 import com.android.internal.protolog.common.ProtoLog;
67 import com.android.wm.shell.common.ShellExecutor;
68 import com.android.wm.shell.common.pip.PipUtils;
69 import com.android.wm.shell.protolog.ShellProtoLogGroup;
70 import com.android.wm.shell.shared.TransitionUtil;
71 import com.android.wm.shell.sysui.ShellInit;
72 import com.android.wm.shell.transition.HomeTransitionObserver;
73 import com.android.wm.shell.transition.Transitions;
74 
75 import java.util.ArrayList;
76 import java.util.function.Consumer;
77 
78 /**
79  * Handles the Recents (overview) animation. Only one of these can run at a time. A recents
80  * transition must be created via {@link #startRecentsTransition}. Anything else will be ignored.
81  */
82 public class RecentsTransitionHandler implements Transitions.TransitionHandler {
83     private static final String TAG = "RecentsTransitionHandler";
84 
85     private final Transitions mTransitions;
86     private final ShellExecutor mExecutor;
87     @Nullable
88     private final RecentTasksController mRecentTasksController;
89     private IApplicationThread mAnimApp = null;
90     private final ArrayList<RecentsController> mControllers = new ArrayList<>();
91     private final ArrayList<RecentsTransitionStateListener> mStateListeners = new ArrayList<>();
92 
93     /**
94      * List of other handlers which might need to mix recents with other things. These are checked
95      * in the order they are added. Ideally there should only be one.
96      */
97     private final ArrayList<RecentsMixedHandler> mMixers = new ArrayList<>();
98 
99     private final HomeTransitionObserver mHomeTransitionObserver;
100     private @Nullable Color mBackgroundColor;
101 
RecentsTransitionHandler(ShellInit shellInit, Transitions transitions, @Nullable RecentTasksController recentTasksController, HomeTransitionObserver homeTransitionObserver)102     public RecentsTransitionHandler(ShellInit shellInit, Transitions transitions,
103             @Nullable RecentTasksController recentTasksController,
104             HomeTransitionObserver homeTransitionObserver) {
105         mTransitions = transitions;
106         mExecutor = transitions.getMainExecutor();
107         mRecentTasksController = recentTasksController;
108         mHomeTransitionObserver = homeTransitionObserver;
109         if (!Transitions.ENABLE_SHELL_TRANSITIONS) return;
110         if (recentTasksController == null) return;
111         shellInit.addInitCallback(() -> {
112             recentTasksController.setTransitionHandler(this);
113             transitions.addHandler(this);
114         }, this);
115     }
116 
117     /** Register a mixer handler. {@see RecentsMixedHandler}*/
addMixer(RecentsMixedHandler mixer)118     public void addMixer(RecentsMixedHandler mixer) {
119         mMixers.add(mixer);
120     }
121 
122     /** Unregister a Mixed Handler */
removeMixer(RecentsMixedHandler mixer)123     public void removeMixer(RecentsMixedHandler mixer) {
124         mMixers.remove(mixer);
125     }
126 
127     /** Adds the callback for receiving the state change of transition. */
addTransitionStateListener(RecentsTransitionStateListener listener)128     public void addTransitionStateListener(RecentsTransitionStateListener listener) {
129         mStateListeners.add(listener);
130     }
131 
132     /**
133      * Sets a background color on the transition root layered behind the outgoing task. {@code null}
134      * may be used to clear any previously set colors to avoid showing a background at all. The
135      * color is always shown at full opacity.
136      */
setTransitionBackgroundColor(@ullable Color color)137     public void setTransitionBackgroundColor(@Nullable Color color) {
138         mBackgroundColor = color;
139     }
140 
141     @VisibleForTesting
startRecentsTransition(PendingIntent intent, Intent fillIn, Bundle options, IApplicationThread appThread, IRecentsAnimationRunner listener)142     public IBinder startRecentsTransition(PendingIntent intent, Intent fillIn, Bundle options,
143             IApplicationThread appThread, IRecentsAnimationRunner listener) {
144         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
145                 "RecentsTransitionHandler.startRecentsTransition");
146 
147         // only care about latest one.
148         mAnimApp = appThread;
149         WindowContainerTransaction wct = new WindowContainerTransaction();
150         wct.sendPendingIntent(intent, fillIn, options);
151         final RecentsController controller = new RecentsController(listener);
152         RecentsMixedHandler mixer = null;
153         Consumer<IBinder> setTransitionForMixer = null;
154         for (int i = 0; i < mMixers.size(); ++i) {
155             setTransitionForMixer = mMixers.get(i).handleRecentsRequest(wct);
156             if (setTransitionForMixer != null) {
157                 mixer = mMixers.get(i);
158                 break;
159             }
160         }
161         final IBinder transition = mTransitions.startTransition(TRANSIT_TO_FRONT, wct,
162                 mixer == null ? this : mixer);
163         for (int i = 0; i < mStateListeners.size(); i++) {
164             mStateListeners.get(i).onTransitionStarted(transition);
165         }
166         if (mixer != null) {
167             setTransitionForMixer.accept(transition);
168         }
169         if (transition != null) {
170             controller.setTransition(transition);
171             mControllers.add(controller);
172         } else {
173             controller.cancel("startRecentsTransition");
174         }
175         return transition;
176     }
177 
178     @Override
handleRequest(IBinder transition, TransitionRequestInfo request)179     public WindowContainerTransaction handleRequest(IBinder transition,
180             TransitionRequestInfo request) {
181         if (mControllers.isEmpty()) {
182             // Ignore if there is no running recents transition
183             return null;
184         }
185         final RecentsController controller = mControllers.get(mControllers.size() - 1);
186         controller.handleMidTransitionRequest(request);
187         return null;
188     }
189 
findController(IBinder transition)190     private int findController(IBinder transition) {
191         for (int i = mControllers.size() - 1; i >= 0; --i) {
192             if (mControllers.get(i).mTransition == transition) return i;
193         }
194         return -1;
195     }
196 
197     @Override
startAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction startTransaction, SurfaceControl.Transaction finishTransaction, Transitions.TransitionFinishCallback finishCallback)198     public boolean startAnimation(IBinder transition, TransitionInfo info,
199             SurfaceControl.Transaction startTransaction,
200             SurfaceControl.Transaction finishTransaction,
201             Transitions.TransitionFinishCallback finishCallback) {
202         final int controllerIdx = findController(transition);
203         if (controllerIdx < 0) {
204             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
205                     "RecentsTransitionHandler.startAnimation: no controller found");
206             return false;
207         }
208         final RecentsController controller = mControllers.get(controllerIdx);
209         final IApplicationThread animApp = mAnimApp;
210         mAnimApp = null;
211         if (!controller.start(info, startTransaction, finishTransaction, finishCallback)) {
212             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
213                     "RecentsTransitionHandler.startAnimation: failed to start animation");
214             return false;
215         }
216         Transitions.setRunningRemoteTransitionDelegate(animApp);
217         return true;
218     }
219 
220     @Override
mergeAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t, IBinder mergeTarget, Transitions.TransitionFinishCallback finishCallback)221     public void mergeAnimation(IBinder transition, TransitionInfo info,
222             SurfaceControl.Transaction t, IBinder mergeTarget,
223             Transitions.TransitionFinishCallback finishCallback) {
224         final int targetIdx = findController(mergeTarget);
225         if (targetIdx < 0) {
226             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
227                     "RecentsTransitionHandler.mergeAnimation: no controller found");
228             return;
229         }
230         final RecentsController controller = mControllers.get(targetIdx);
231         controller.merge(info, t, finishCallback);
232     }
233 
234     @Override
onTransitionConsumed(IBinder transition, boolean aborted, SurfaceControl.Transaction finishTransaction)235     public void onTransitionConsumed(IBinder transition, boolean aborted,
236             SurfaceControl.Transaction finishTransaction) {
237         // Only one recents transition can be handled at a time, but currently the first transition
238         // will trigger a no-op in the second transition which holds the active recents animation
239         // runner on the launcher side.  For now, cancel all existing animations to ensure we
240         // don't get into a broken state with an orphaned animation runner, and later we can try to
241         // merge the latest transition into the currently running one
242         for (int i = mControllers.size() - 1; i >= 0; i--) {
243             mControllers.get(i).cancel("onTransitionConsumed");
244         }
245     }
246 
247     /** There is only one of these and it gets reset on finish. */
248     private class RecentsController extends IRecentsAnimationController.Stub {
249         private final int mInstanceId;
250 
251         private IRecentsAnimationRunner mListener;
252         private IBinder.DeathRecipient mDeathHandler;
253         private Transitions.TransitionFinishCallback mFinishCB = null;
254         private SurfaceControl.Transaction mFinishTransaction = null;
255 
256         /**
257          * List of tasks that we are switching away from via this transition. Upon finish, these
258          * pausing tasks will become invisible.
259          * These need to be ordered since the order must be restored if there is no task-switch.
260          */
261         private ArrayList<TaskState> mPausingTasks = null;
262 
263         /**
264          * List of tasks were pausing but closed in a subsequent merged transition. If a
265          * closing task is reopened, the leash is not initially hidden since it is already
266          * visible.
267          */
268         private ArrayList<TaskState> mClosingTasks = null;
269 
270         /**
271          * List of tasks that we are switching to. Upon finish, these will remain visible and
272          * on top.
273          */
274         private ArrayList<TaskState> mOpeningTasks = null;
275 
276         private WindowContainerToken mPipTask = null;
277         private int mPipTaskId = -1;
278         private WindowContainerToken mRecentsTask = null;
279         private int mRecentsTaskId = -1;
280         private TransitionInfo mInfo = null;
281         private boolean mOpeningSeparateHome = false;
282         private boolean mPausingSeparateHome = false;
283         private ArrayMap<SurfaceControl, SurfaceControl> mLeashMap = null;
284         private PictureInPictureSurfaceTransaction mPipTransaction = null;
285         private IBinder mTransition = null;
286         private boolean mKeyguardLocked = false;
287         private boolean mWillFinishToHome = false;
288         private Transitions.TransitionHandler mTakeoverHandler = null;
289 
290         /** The animation is idle, waiting for the user to choose a task to switch to. */
291         private static final int STATE_NORMAL = 0;
292 
293         /** The user chose a new task to switch to and the animation is animating to it. */
294         private static final int STATE_NEW_TASK = 1;
295 
296         /** The latest state that the recents animation is operating in. */
297         private int mState = STATE_NORMAL;
298 
299         // Snapshots taken when a new display change transition is requested, prior to the display
300         // change being applied.  This pending set of snapshots will only be applied when cancel is
301         // next called.
302         private Pair<int[], TaskSnapshot[]> mPendingPauseSnapshotsForCancel;
303 
RecentsController(IRecentsAnimationRunner listener)304         RecentsController(IRecentsAnimationRunner listener) {
305             mInstanceId = System.identityHashCode(this);
306             mListener = listener;
307             mDeathHandler = () -> {
308                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
309                         "[%d] RecentsController.DeathRecipient: binder died", mInstanceId);
310                 finish(mWillFinishToHome, false /* leaveHint */, null /* finishCb */);
311             };
312             try {
313                 mListener.asBinder().linkToDeath(mDeathHandler, 0 /* flags */);
314             } catch (RemoteException e) {
315                 Slog.e(TAG, "RecentsController: failed to link to death", e);
316                 mListener = null;
317             }
318         }
319 
setTransition(IBinder transition)320         void setTransition(IBinder transition) {
321             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
322                     "[%d] RecentsController.setTransition: id=%s", mInstanceId, transition);
323             mTransition = transition;
324         }
325 
cancel(String reason)326         void cancel(String reason) {
327             // restoring (to-home = false) involves submitting more WM changes, so by default, use
328             // toHome = true when canceling.
329             cancel(true /* toHome */, false /* withScreenshots */, reason);
330         }
331 
cancel(boolean toHome, boolean withScreenshots, String reason)332         void cancel(boolean toHome, boolean withScreenshots, String reason) {
333             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
334                     "[%d] RecentsController.cancel: toHome=%b reason=%s",
335                     mInstanceId, toHome, reason);
336             if (mListener != null) {
337                 if (withScreenshots) {
338                     sendCancelWithSnapshots();
339                 } else {
340                     sendCancel(null, null);
341                 }
342             }
343             if (mFinishCB != null) {
344                 finishInner(toHome, false /* userLeave */, null /* finishCb */);
345             } else {
346                 cleanUp();
347             }
348         }
349 
350         /**
351          * Sends a cancel message to the recents animation with snapshots. Used to trigger a
352          * "replace-with-screenshot" like behavior.
353          */
sendCancelWithSnapshots()354         private boolean sendCancelWithSnapshots() {
355             Pair<int[], TaskSnapshot[]> snapshots = mPendingPauseSnapshotsForCancel != null
356                     ? mPendingPauseSnapshotsForCancel
357                     : getSnapshotsForPausingTasks();
358             return sendCancel(snapshots.first, snapshots.second);
359         }
360 
361         /**
362          * Snapshots the pausing tasks and returns the mapping of the taskId -> snapshot.
363          */
getSnapshotsForPausingTasks()364         private Pair<int[], TaskSnapshot[]> getSnapshotsForPausingTasks() {
365             int[] taskIds = null;
366             TaskSnapshot[] snapshots = null;
367             if (mPausingTasks != null && mPausingTasks.size() > 0) {
368                 taskIds = new int[mPausingTasks.size()];
369                 snapshots = new TaskSnapshot[mPausingTasks.size()];
370                 try {
371                     for (int i = 0; i < mPausingTasks.size(); ++i) {
372                         TaskState state = mPausingTasks.get(0);
373                         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
374                                 "[%d] RecentsController.sendCancel: Snapshotting task=%d",
375                                 mInstanceId, state.mTaskInfo.taskId);
376                         snapshots[i] = ActivityTaskManager.getService().takeTaskSnapshot(
377                                 state.mTaskInfo.taskId, true /* updateCache */);
378                     }
379                 } catch (RemoteException e) {
380                     taskIds = null;
381                     snapshots = null;
382                 }
383             }
384             return new Pair(taskIds, snapshots);
385         }
386 
387         /**
388          * Sends a cancel message to the recents animation.
389          */
sendCancel(@ullable int[] taskIds, @Nullable TaskSnapshot[] taskSnapshots)390         private boolean sendCancel(@Nullable int[] taskIds,
391                 @Nullable TaskSnapshot[] taskSnapshots) {
392             try {
393                 final String cancelDetails = taskSnapshots != null ? "with snapshots" : "";
394                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
395                         "[%d] RecentsController.cancel: calling onAnimationCanceled %s",
396                         mInstanceId, cancelDetails);
397                 mListener.onAnimationCanceled(taskIds, taskSnapshots);
398                 return true;
399             } catch (RemoteException e) {
400                 Slog.e(TAG, "Error canceling recents animation", e);
401                 return false;
402             }
403         }
404 
405         /**
406          * Cleans up the recents transition.  This should generally not be called directly
407          * to cancel a transition after it has started, instead callers should call one of
408          * the cancel() methods to ensure that Launcher is notified.
409          */
cleanUp()410         void cleanUp() {
411             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
412                     "[%d] RecentsController.cleanup", mInstanceId);
413             if (mListener != null && mDeathHandler != null) {
414                 mListener.asBinder().unlinkToDeath(mDeathHandler, 0 /* flags */);
415                 mDeathHandler = null;
416             }
417             mListener = null;
418             mFinishCB = null;
419             // clean-up leash surfacecontrols and anything that might reference them.
420             if (mLeashMap != null) {
421                 for (int i = 0; i < mLeashMap.size(); ++i) {
422                     mLeashMap.valueAt(i).release();
423                 }
424                 mLeashMap = null;
425             }
426             mFinishTransaction = null;
427             mPausingTasks = null;
428             mClosingTasks = null;
429             mOpeningTasks = null;
430             mInfo = null;
431             mTransition = null;
432             mPendingPauseSnapshotsForCancel = null;
433             mControllers.remove(this);
434             for (int i = 0; i < mStateListeners.size(); i++) {
435                 mStateListeners.get(i).onAnimationStateChanged(false);
436             }
437         }
438 
start(TransitionInfo info, SurfaceControl.Transaction t, SurfaceControl.Transaction finishT, Transitions.TransitionFinishCallback finishCB)439         boolean start(TransitionInfo info, SurfaceControl.Transaction t,
440                 SurfaceControl.Transaction finishT, Transitions.TransitionFinishCallback finishCB) {
441             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
442                     "[%d] RecentsController.start", mInstanceId);
443             if (mListener == null || mTransition == null) {
444                 Slog.e(TAG, "Missing listener or transition, hasListener=" + (mListener != null) +
445                         " hasTransition=" + (mTransition != null));
446                 cancel("No listener (" + (mListener == null)
447                         + ") or no transition (" + (mTransition == null) + ")");
448                 return false;
449             }
450             // First see if this is a valid recents transition.
451             boolean hasPausingTasks = false;
452             for (int i = 0; i < info.getChanges().size(); ++i) {
453                 final TransitionInfo.Change change = info.getChanges().get(i);
454                 if (TransitionUtil.isWallpaper(change)) continue;
455                 if (TransitionUtil.isClosingType(change.getMode())) {
456                     hasPausingTasks = true;
457                     continue;
458                 }
459                 final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
460                 if (taskInfo != null && taskInfo.topActivityType == ACTIVITY_TYPE_RECENTS) {
461                     mRecentsTask = taskInfo.token;
462                     mRecentsTaskId = taskInfo.taskId;
463                 } else if (taskInfo != null && taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
464                     mRecentsTask = taskInfo.token;
465                     mRecentsTaskId = taskInfo.taskId;
466                 }
467             }
468             if (mRecentsTask == null && !hasPausingTasks) {
469                 // Recents is already running apparently, so this is a no-op.
470                 Slog.e(TAG, "Tried to start recents while it is already running.");
471                 cancel("No recents task and no pausing tasks");
472                 return false;
473             }
474 
475             mInfo = info;
476             mFinishCB = finishCB;
477             mFinishTransaction = finishT;
478             mPausingTasks = new ArrayList<>();
479             mClosingTasks = new ArrayList<>();
480             mOpeningTasks = new ArrayList<>();
481             mLeashMap = new ArrayMap<>();
482             mKeyguardLocked = (info.getFlags() & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0;
483 
484             int closingSplitTaskId = INVALID_TASK_ID;
485             final ArrayList<RemoteAnimationTarget> apps = new ArrayList<>();
486             final ArrayList<RemoteAnimationTarget> wallpapers = new ArrayList<>();
487             TransitionUtil.LeafTaskFilter leafTaskFilter = new TransitionUtil.LeafTaskFilter();
488             // About layering: we divide up the "layer space" into 3 regions (each the size of
489             // the change count). This lets us categorize things into above/below/between
490             // while maintaining their relative ordering.
491             final int belowLayers = info.getChanges().size();
492             final int middleLayers = info.getChanges().size() * 2;
493             final int aboveLayers = info.getChanges().size() * 3;
494 
495             // Add a background color to each transition root in this transition.
496             if (mBackgroundColor != null) {
497                 info.getChanges().stream()
498                         .mapToInt((change) -> TransitionUtil.rootIndexFor(change, info))
499                         .distinct()
500                         .mapToObj((rootIndex) -> info.getRoot(rootIndex).getLeash())
501                         .forEach((root) -> createBackgroundSurface(t, root, middleLayers));
502             }
503 
504             for (int i = 0; i < info.getChanges().size(); ++i) {
505                 final TransitionInfo.Change change = info.getChanges().get(i);
506                 final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
507                 if (TransitionUtil.isWallpaper(change)) {
508                     final RemoteAnimationTarget target = TransitionUtil.newTarget(change,
509                             // wallpapers go into the "below" layer space
510                             belowLayers - i, info, t, mLeashMap);
511                     wallpapers.add(target);
512                     // Make all the wallpapers opaque since we want them visible from the start
513                     t.setAlpha(target.leash, 1);
514                 } else if (leafTaskFilter.test(change)) {
515                     // start by putting everything into the "below" layer space.
516                     final RemoteAnimationTarget target = TransitionUtil.newTarget(change,
517                             belowLayers - i, info, t, mLeashMap);
518                     apps.add(target);
519                     if (TransitionUtil.isClosingType(change.getMode())) {
520                         mPausingTasks.add(new TaskState(change, target.leash));
521                         closingSplitTaskId = change.getTaskInfo().taskId;
522                         if (taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
523                             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
524                                     "  adding pausing leaf home taskId=%d", taskInfo.taskId);
525                             // This can only happen if we have a separate recents/home (3p launcher)
526                             mPausingSeparateHome = true;
527                         } else {
528                             final int layer = aboveLayers - i;
529                             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
530                                     "  adding pausing leaf taskId=%d at layer=%d",
531                                     taskInfo.taskId, layer);
532                             // raise closing (pausing) task to "above" layer so it isn't covered
533                             t.setLayer(target.leash, layer);
534                         }
535                         if (taskInfo.pictureInPictureParams != null
536                                 && taskInfo.pictureInPictureParams.isAutoEnterEnabled()) {
537                             mPipTask = taskInfo.token;
538                         }
539                     } else if (taskInfo != null
540                             && taskInfo.topActivityType == ACTIVITY_TYPE_RECENTS) {
541                         final int layer = middleLayers - i;
542                         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
543                                 "  setting recents activity layer=%d", layer);
544                         // There's a 3p launcher, so make sure recents goes above that, but under
545                         // the pausing apps.
546                         t.setLayer(target.leash, layer);
547                     } else if (taskInfo != null && taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
548                         // do nothing
549                     } else if (TransitionUtil.isOpeningType(change.getMode())) {
550                         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
551                                 "  adding opening leaf taskId=%d", taskInfo.taskId);
552                         mOpeningTasks.add(new TaskState(change, target.leash));
553                     }
554                 } else if (taskInfo != null && TransitionInfo.isIndependent(change, info)) {
555                     // Root tasks
556                     if (TransitionUtil.isClosingType(change.getMode())) {
557                         final int layer = aboveLayers - i;
558                         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
559                                 "  adding pausing taskId=%d at layer=%d", taskInfo.taskId, layer);
560                         // raise closing (pausing) task to "above" layer so it isn't covered
561                         t.setLayer(change.getLeash(), layer);
562                         mPausingTasks.add(new TaskState(change, null /* leash */));
563                     } else if (TransitionUtil.isOpeningType(change.getMode())) {
564                         final int layer = belowLayers - i;
565                         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
566                                 "  adding opening taskId=%d at layer=%d", taskInfo.taskId, layer);
567                         // Put into the "below" layer space.
568                         t.setLayer(change.getLeash(), layer);
569                         mOpeningTasks.add(new TaskState(change, null /* leash */));
570                     } else {
571                         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
572                                 "  unhandled root taskId=%d", taskInfo.taskId);
573                     }
574                 } else if (TransitionUtil.isDividerBar(change)) {
575                     final RemoteAnimationTarget target = TransitionUtil.newTarget(change,
576                             belowLayers - i, info, t, mLeashMap);
577                     // Add this as a app and we will separate them on launcher side by window type.
578                     apps.add(target);
579                 } else {
580                     ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
581                             "  unhandled change taskId=%d",
582                             taskInfo != null ? taskInfo.taskId : -1);
583                 }
584             }
585             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
586                     "Applying transaction=%d", t.getId());
587             t.apply();
588 
589             mTakeoverHandler = mTransitions.getHandlerForTakeover(mTransition, info);
590 
591             Bundle b = new Bundle(2 /*capacity*/);
592             b.putParcelable(KEY_EXTRA_SPLIT_BOUNDS,
593                     mRecentTasksController.getSplitBoundsForTaskId(closingSplitTaskId));
594             b.putBoolean(KEY_EXTRA_SHELL_CAN_HAND_OFF_ANIMATION, mTakeoverHandler != null);
595             try {
596                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
597                         "[%d] RecentsController.start: calling onAnimationStart with %d apps",
598                         mInstanceId, apps.size());
599                 mListener.onAnimationStart(this,
600                         apps.toArray(new RemoteAnimationTarget[apps.size()]),
601                         wallpapers.toArray(new RemoteAnimationTarget[wallpapers.size()]),
602                         new Rect(0, 0, 0, 0), new Rect(), b);
603                 for (int i = 0; i < mStateListeners.size(); i++) {
604                     mStateListeners.get(i).onAnimationStateChanged(true);
605                 }
606             } catch (RemoteException e) {
607                 Slog.e(TAG, "Error starting recents animation", e);
608                 cancel("onAnimationStart() failed");
609             }
610             return true;
611         }
612 
613         @Override
handOffAnimation( RemoteAnimationTarget[] targets, WindowAnimationState[] states)614         public void handOffAnimation(
615                 RemoteAnimationTarget[] targets, WindowAnimationState[] states) {
616             mExecutor.execute(() -> {
617                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
618                         "[%d] RecentsController.handOffAnimation", mInstanceId);
619 
620                 if (mTakeoverHandler == null) {
621                     Slog.e(TAG, "Tried to hand off an animation without a valid takeover "
622                             + "handler.");
623                     return;
624                 }
625 
626                 if (targets.length != states.length) {
627                     Slog.e(TAG, "Tried to hand off an animation, but the number of targets "
628                             + "(" + targets.length + ") doesn't match the number of states "
629                             + "(" + states.length + ")");
630                     return;
631                 }
632 
633                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
634                         "[%d] RecentsController.handOffAnimation: got %d states for %d "
635                                 + "changes", mInstanceId, states.length, mInfo.getChanges().size());
636                 WindowAnimationState[] updatedStates =
637                         new WindowAnimationState[mInfo.getChanges().size()];
638 
639                 // Ensure that the ordering of animation states is the same as that of  matching
640                 // changes in mInfo. prefixOrderIndex is set up in reverse order to that of the
641                 // changes, so that's what we use to get to the correct ordering.
642                 for (int i = 0; i < targets.length; i++) {
643                     RemoteAnimationTarget target = targets[i];
644                     updatedStates[updatedStates.length - target.prefixOrderIndex] = states[i];
645                 }
646 
647                 Transitions.TransitionFinishCallback finishCB = mFinishCB;
648                 // Reset the callback here, so any stray calls that aren't coming from the new
649                 // handler are ignored.
650                 mFinishCB = null;
651 
652                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
653                         "[%d] RecentsController.handOffAnimation: calling "
654                                 + "takeOverAnimation with %d states", mInstanceId,
655                         updatedStates.length);
656                 mTakeoverHandler.takeOverAnimation(
657                         mTransition, mInfo, new SurfaceControl.Transaction(),
658                         wct -> {
659                             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
660                                     "[%d] RecentsController.handOffAnimation: finish "
661                                             + "callback", mInstanceId);
662                             // Set the callback once again so we can finish correctly.
663                             mFinishCB = finishCB;
664                             finishInner(true /* toHome */, false /* userLeave */,
665                                     null /* finishCb */);
666                         }, updatedStates);
667             });
668         }
669 
670         /**
671          * Updates this controller when a new transition is requested mid-recents transition.
672          */
handleMidTransitionRequest(TransitionRequestInfo request)673         void handleMidTransitionRequest(TransitionRequestInfo request) {
674             if (request.getType() == TRANSIT_CHANGE && request.getDisplayChange() != null) {
675                 final TransitionRequestInfo.DisplayChange dispChange = request.getDisplayChange();
676                 if (dispChange.getStartRotation() != dispChange.getEndRotation()) {
677                     ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
678                             "[%d] RecentsController.prepareForMerge: "
679                                     + "Snapshot due to requested display change",
680                             mInstanceId);
681                     mPendingPauseSnapshotsForCancel = getSnapshotsForPausingTasks();
682                 }
683             }
684         }
685 
686         @SuppressLint("NewApi")
merge(TransitionInfo info, SurfaceControl.Transaction t, Transitions.TransitionFinishCallback finishCallback)687         void merge(TransitionInfo info, SurfaceControl.Transaction t,
688                 Transitions.TransitionFinishCallback finishCallback) {
689             if (mFinishCB == null) {
690                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
691                         "[%d] RecentsController.merge: skip, no finish callback",
692                         mInstanceId);
693                 // This was no-op'd (likely a repeated start) and we've already sent finish.
694                 return;
695             }
696             if (info.getType() == TRANSIT_SLEEP) {
697                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
698                         "[%d] RecentsController.merge: transit_sleep", mInstanceId);
699                 // A sleep event means we need to stop animations immediately, so cancel here.
700                 cancel("transit_sleep");
701                 return;
702             }
703             if (mKeyguardLocked || (info.getFlags() & KEYGUARD_VISIBILITY_TRANSIT_FLAGS) != 0) {
704                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
705                         "[%d] RecentsController.merge: keyguard is locked", mInstanceId);
706                 // We will not accept new changes if we are swiping over the keyguard.
707                 cancel(true /* toHome */, false /* withScreenshots */, "keyguard_locked");
708                 return;
709             }
710             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
711                     "[%d] RecentsController.merge", mInstanceId);
712             // Keep all tasks in one list because order matters.
713             ArrayList<TransitionInfo.Change> openingTasks = null;
714             IntArray openingTaskIsLeafs = null;
715             ArrayList<TransitionInfo.Change> closingTasks = null;
716             mOpeningSeparateHome = false;
717             TransitionInfo.Change recentsOpening = null;
718             boolean foundRecentsClosing = false;
719             boolean hasChangingApp = false;
720             final TransitionUtil.LeafTaskFilter leafTaskFilter =
721                     new TransitionUtil.LeafTaskFilter();
722             boolean hasTaskChange = false;
723             for (int i = 0; i < info.getChanges().size(); ++i) {
724                 final TransitionInfo.Change change = info.getChanges().get(i);
725                 final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
726                 if (taskInfo != null
727                         && taskInfo.configuration.windowConfiguration.isAlwaysOnTop()) {
728                     // Tasks that are always on top (e.g. bubbles), will handle their own transition
729                     // as they are on top of everything else. So cancel the merge here.
730                     cancel(false /* toHome */, false /* withScreenshots */,
731                             "task #" + taskInfo.taskId + " is always_on_top");
732                     return;
733                 }
734                 final boolean isRootTask = taskInfo != null
735                         && TransitionInfo.isIndependent(change, info);
736                 final boolean isRecentsTask = mRecentsTask != null
737                         && mRecentsTask.equals(change.getContainer());
738                 hasTaskChange = hasTaskChange || isRootTask;
739                 final boolean isLeafTask = leafTaskFilter.test(change);
740                 if (TransitionUtil.isOpeningType(change.getMode())
741                         || TransitionUtil.isOrderOnly(change)) {
742                     if (isRecentsTask) {
743                         recentsOpening = change;
744                     } else if (isRootTask || isLeafTask) {
745                         if (isLeafTask && taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
746                             // This is usually a 3p launcher
747                             mOpeningSeparateHome = true;
748                         }
749                         if (openingTasks == null) {
750                             openingTasks = new ArrayList<>();
751                             openingTaskIsLeafs = new IntArray();
752                         }
753                         openingTasks.add(change);
754                         openingTaskIsLeafs.add(isLeafTask ? 1 : 0);
755                     }
756                 } else if (TransitionUtil.isClosingType(change.getMode())) {
757                     if (isRecentsTask) {
758                         foundRecentsClosing = true;
759                     } else if (isRootTask || isLeafTask) {
760                         if (closingTasks == null) {
761                             closingTasks = new ArrayList<>();
762                         }
763                         closingTasks.add(change);
764                     }
765                 } else if (change.getMode() == TRANSIT_CHANGE) {
766                     // Finish recents animation if the display is changed, so the default
767                     // transition handler can play the animation such as rotation effect.
768                     if (change.hasFlags(TransitionInfo.FLAG_IS_DISPLAY)
769                             && info.getType() == TRANSIT_CHANGE) {
770                         // This call to cancel will use the screenshots taken preemptively in
771                         // handleMidTransitionRequest() prior to the display changing
772                         cancel(mWillFinishToHome, true /* withScreenshots */, "display change");
773                         return;
774                     }
775                     // Don't consider order-only & non-leaf changes as changing apps.
776                     if (!TransitionUtil.isOrderOnly(change) && isLeafTask) {
777                         hasChangingApp = true;
778                     } else if (isLeafTask && taskInfo.topActivityType == ACTIVITY_TYPE_HOME
779                             && !isRecentsTask ) {
780                         // Unless it is a 3p launcher. This means that the 3p launcher was already
781                         // visible (eg. the "pausing" task is translucent over the 3p launcher).
782                         // Treat it as if we are "re-opening" the 3p launcher.
783                         if (openingTasks == null) {
784                             openingTasks = new ArrayList<>();
785                             openingTaskIsLeafs = new IntArray();
786                         }
787                         openingTasks.add(change);
788                         openingTaskIsLeafs.add(1);
789                     }
790                 }
791             }
792             if (hasChangingApp && foundRecentsClosing) {
793                 // This happens when a visible app is expanding (usually PiP). In this case,
794                 // that transition probably has a special-purpose animation, so finish recents
795                 // now and let it do its animation (since recents is going to be occluded).
796                 sendCancelWithSnapshots();
797                 mExecutor.executeDelayed(
798                         () -> finishInner(true /* toHome */, false /* userLeaveHint */,
799                                 null /* finishCb */), 0);
800                 return;
801             }
802             if (recentsOpening != null) {
803                 // the recents task re-appeared. This happens if the user gestures before the
804                 // task-switch (NEW_TASK) animation finishes.
805                 if (mState == STATE_NORMAL) {
806                     Slog.e(TAG, "Returning to recents while recents is already idle.");
807                 }
808                 if (closingTasks == null || closingTasks.size() == 0) {
809                     Slog.e(TAG, "Returning to recents without closing any opening tasks.");
810                 }
811                 // Setup may hide it initially since it doesn't know that overview was still active.
812                 t.show(recentsOpening.getLeash());
813                 t.setAlpha(recentsOpening.getLeash(), 1.f);
814                 mState = STATE_NORMAL;
815             }
816             boolean didMergeThings = false;
817             if (closingTasks != null) {
818                 // Potentially cancelling a task-switch. Move the tasks back to mPausing if they
819                 // are in mOpening.
820                 for (int i = 0; i < closingTasks.size(); ++i) {
821                     final TransitionInfo.Change change = closingTasks.get(i);
822                     final int pausingIdx = TaskState.indexOf(mPausingTasks, change);
823                     if (pausingIdx >= 0) {
824                         // We are closing the pausing task, but it is still visible and can be
825                         // restart by another transition prior to this transition finishing
826                         final TaskState closingTask = mPausingTasks.remove(pausingIdx);
827                         mClosingTasks.add(closingTask);
828                         didMergeThings = true;
829                         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
830                                 "  closing pausing taskId=%d", change.getTaskInfo().taskId);
831                         continue;
832                     }
833                     int openingIdx = TaskState.indexOf(mOpeningTasks, change);
834                     if (openingIdx < 0) {
835                         Slog.w(TAG, "Closing a task that wasn't opening, this may be split or"
836                                 + " something unexpected: " + change.getTaskInfo().taskId);
837                         continue;
838                     }
839                     final TaskState openingTask = mOpeningTasks.remove(openingIdx);
840                     ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
841                             "  pausing opening %staskId=%d", openingTask.isLeaf() ? "leaf " : "",
842                             openingTask.mTaskInfo.taskId);
843                     mPausingTasks.add(openingTask);
844                     didMergeThings = true;
845                 }
846             }
847             RemoteAnimationTarget[] appearedTargets = null;
848             if (openingTasks != null && openingTasks.size() > 0) {
849                 // Switching to some new tasks, add to mOpening and remove from mPausing. Also,
850                 // enter NEW_TASK state since this will start the switch-to animation.
851                 final int layer = mInfo.getChanges().size() * 3;
852                 int openingLeafCount = 0;
853                 for (int i = 0; i < openingTaskIsLeafs.size(); ++i) {
854                     openingLeafCount += openingTaskIsLeafs.get(i);
855                 }
856                 if (openingLeafCount > 0) {
857                     appearedTargets = new RemoteAnimationTarget[openingLeafCount];
858                 }
859                 int nextTargetIdx = 0;
860                 for (int i = 0; i < openingTasks.size(); ++i) {
861                     final TransitionInfo.Change change = openingTasks.get(i);
862                     final boolean isLeaf = openingTaskIsLeafs.get(i) == 1;
863                     final int closingIdx = TaskState.indexOf(mClosingTasks, change);
864                     if (closingIdx >= 0) {
865                         // Remove opening tasks from closing set
866                         mClosingTasks.remove(closingIdx);
867                     }
868                     final int pausingIdx = TaskState.indexOf(mPausingTasks, change);
869                     if (pausingIdx >= 0) {
870                         // Something is showing/opening a previously-pausing app.
871                         if (isLeaf) {
872                             appearedTargets[nextTargetIdx++] = TransitionUtil.newTarget(
873                                     change, layer, mPausingTasks.get(pausingIdx).mLeash);
874                         }
875                         final TaskState pausingTask = mPausingTasks.remove(pausingIdx);
876                         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
877                                 "  opening pausing %staskId=%d", isLeaf ? "leaf " : "",
878                                 pausingTask.mTaskInfo.taskId);
879                         mOpeningTasks.add(pausingTask);
880                         // Setup hides opening tasks initially, so make it visible again (since we
881                         // are already showing it).
882                         t.show(change.getLeash());
883                         t.setAlpha(change.getLeash(), 1.f);
884                     } else if (isLeaf) {
885                         // We are receiving new opening leaf tasks, so convert to onTasksAppeared.
886                         final RemoteAnimationTarget target = TransitionUtil.newTarget(
887                                 change, layer, info, t, mLeashMap);
888                         appearedTargets[nextTargetIdx++] = target;
889                         // reparent into the original `mInfo` since that's where we are animating.
890                         final int rootIdx = TransitionUtil.rootIndexFor(change, mInfo);
891                         final boolean wasClosing = closingIdx >= 0;
892                         t.reparent(target.leash, mInfo.getRoot(rootIdx).getLeash());
893                         t.setLayer(target.leash, layer);
894                         if (wasClosing) {
895                             // App was previously visible and is closing
896                             t.show(target.leash);
897                             t.setAlpha(target.leash, 1f);
898                             // Also override the task alpha as it was set earlier when dispatching
899                             // the transition and setting up the leash to hide the
900                             t.setAlpha(change.getLeash(), 1f);
901                         } else {
902                             // Hide the animation leash, let the listener show it
903                             t.hide(target.leash);
904                         }
905                         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
906                                 "  opening new leaf taskId=%d wasClosing=%b",
907                                 target.taskId, wasClosing);
908                         mOpeningTasks.add(new TaskState(change, target.leash));
909                     } else {
910                         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
911                                 "  opening new taskId=%d", change.getTaskInfo().taskId);
912                         t.setLayer(change.getLeash(), layer);
913                         // Setup hides opening tasks initially, so make it visible since recents
914                         // is only animating the leafs.
915                         t.show(change.getLeash());
916                         mOpeningTasks.add(new TaskState(change, null));
917                     }
918                 }
919                 didMergeThings = true;
920                 mState = STATE_NEW_TASK;
921             }
922             if (mPausingTasks.isEmpty()) {
923                 // The pausing tasks may be removed by the incoming closing tasks.
924                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
925                         "[%d] RecentsController.merge: empty pausing tasks", mInstanceId);
926             }
927             if (!hasTaskChange) {
928                 // Activity only transition, so consume the merge as it doesn't affect the rest of
929                 // recents.
930                 Slog.d(TAG, "Got an activity only transition during recents, so apply directly");
931                 mergeActivityOnly(info, t);
932             } else if (!didMergeThings) {
933                 // Didn't recognize anything in incoming transition so don't merge it.
934                 Slog.w(TAG, "Don't know how to merge this transition, foundRecentsClosing="
935                         + foundRecentsClosing + " recentsTaskId=" + mRecentsTaskId);
936                 if (foundRecentsClosing || mRecentsTaskId < 0) {
937                     mWillFinishToHome = false;
938                     cancel(false /* toHome */, false /* withScreenshots */, "didn't merge");
939                 }
940                 return;
941             }
942             // At this point, we are accepting the merge.
943             t.apply();
944             // not using the incoming anim-only surfaces
945             info.releaseAnimSurfaces();
946             if (appearedTargets != null) {
947                 try {
948                     ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
949                             "[%d] RecentsController.merge: calling onTasksAppeared", mInstanceId);
950                     mListener.onTasksAppeared(appearedTargets);
951                 } catch (RemoteException e) {
952                     Slog.e(TAG, "Error sending appeared tasks to recents animation", e);
953                 }
954             }
955             finishCallback.onTransitionFinished(null /* wct */);
956         }
957 
958         /** For now, just set-up a jump-cut to the new activity. */
mergeActivityOnly(TransitionInfo info, SurfaceControl.Transaction t)959         private void mergeActivityOnly(TransitionInfo info, SurfaceControl.Transaction t) {
960             for (int i = 0; i < info.getChanges().size(); ++i) {
961                 final TransitionInfo.Change change = info.getChanges().get(i);
962                 if (TransitionUtil.isOpeningType(change.getMode())) {
963                     t.show(change.getLeash());
964                     t.setAlpha(change.getLeash(), 1.f);
965                 } else if (TransitionUtil.isClosingType(change.getMode())) {
966                     t.hide(change.getLeash());
967                 }
968             }
969         }
970 
971         @Override
screenshotTask(int taskId)972         public TaskSnapshot screenshotTask(int taskId) {
973             try {
974                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
975                         "[%d] RecentsController.screenshotTask: taskId=%d", mInstanceId, taskId);
976                 return ActivityTaskManager.getService().takeTaskSnapshot(taskId,
977                         true /* updateCache */);
978             } catch (RemoteException e) {
979                 Slog.e(TAG, "Failed to screenshot task", e);
980             }
981             return null;
982         }
983 
984         @Override
setInputConsumerEnabled(boolean enabled)985         public void setInputConsumerEnabled(boolean enabled) {
986             mExecutor.execute(() -> {
987                 if (mFinishCB == null || !enabled) {
988                     ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
989                             "RecentsController.setInputConsumerEnabled: skip, cb?=%b enabled?=%b",
990                             mFinishCB != null, enabled);
991                     return;
992                 }
993                 final int displayId = mInfo.getRootCount() > 0 ? mInfo.getRoot(0).getDisplayId()
994                         : Display.DEFAULT_DISPLAY;
995                 // transient launches don't receive focus automatically. Since we are taking over
996                 // the gesture now, take focus explicitly.
997                 // This also moves recents back to top if the user gestured before a switch
998                 // animation finished.
999                 try {
1000                     ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
1001                             "[%d] RecentsController.setInputConsumerEnabled: set focus to recents",
1002                             mInstanceId);
1003                     ActivityTaskManager.getService().focusTopTask(displayId);
1004                 } catch (RemoteException e) {
1005                     Slog.e(TAG, "Failed to set focused task", e);
1006                 }
1007             });
1008         }
1009 
1010         @Override
setAnimationTargetsBehindSystemBars(boolean behindSystemBars)1011         public void setAnimationTargetsBehindSystemBars(boolean behindSystemBars) {
1012         }
1013 
1014         @Override
setFinishTaskTransaction(int taskId, PictureInPictureSurfaceTransaction finishTransaction, SurfaceControl overlay)1015         public void setFinishTaskTransaction(int taskId,
1016                 PictureInPictureSurfaceTransaction finishTransaction, SurfaceControl overlay) {
1017             mExecutor.execute(() -> {
1018                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
1019                         "[%d] RecentsController.setFinishTaskTransaction: taskId=%d,"
1020                                 + " [mFinishCB is non-null]=%b",
1021                         mInstanceId, taskId, mFinishCB != null);
1022                 if (mFinishCB == null) return;
1023                 mPipTransaction = finishTransaction;
1024                 mPipTaskId = taskId;
1025             });
1026         }
1027 
1028         @Override
1029         @SuppressLint("NewApi")
finish(boolean toHome, boolean sendUserLeaveHint, IResultReceiver finishCb)1030         public void finish(boolean toHome, boolean sendUserLeaveHint, IResultReceiver finishCb) {
1031             mExecutor.execute(() -> finishInner(toHome, sendUserLeaveHint, finishCb));
1032         }
1033 
finishInner(boolean toHome, boolean sendUserLeaveHint, IResultReceiver runnerFinishCb)1034         private void finishInner(boolean toHome, boolean sendUserLeaveHint,
1035                 IResultReceiver runnerFinishCb) {
1036             if (mFinishCB == null) {
1037                 Slog.e(TAG, "Duplicate call to finish");
1038                 return;
1039             }
1040 
1041             boolean returningToApp = !toHome
1042                     && !mWillFinishToHome
1043                     && mPausingTasks != null
1044                     && mState == STATE_NORMAL;
1045             if (returningToApp && allAppsAreTranslucent(mPausingTasks)) {
1046                 mHomeTransitionObserver.notifyHomeVisibilityChanged(true);
1047             } else if (!toHome) {
1048                 // For some transitions, we may have notified home activity that it became visible.
1049                 // We need to notify the observer that we are no longer going home.
1050                 mHomeTransitionObserver.notifyHomeVisibilityChanged(false);
1051             }
1052             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
1053                     "[%d] RecentsController.finishInner: toHome=%b userLeave=%b "
1054                             + "willFinishToHome=%b state=%d",
1055                     mInstanceId, toHome, sendUserLeaveHint, mWillFinishToHome, mState);
1056             final Transitions.TransitionFinishCallback finishCB = mFinishCB;
1057             mFinishCB = null;
1058 
1059             final SurfaceControl.Transaction t = mFinishTransaction;
1060             final WindowContainerTransaction wct = new WindowContainerTransaction();
1061 
1062             if (mKeyguardLocked && mRecentsTask != null) {
1063                 if (toHome) wct.reorder(mRecentsTask, true /* toTop */);
1064                 else wct.restoreTransientOrder(mRecentsTask);
1065             }
1066             if (returningToApp) {
1067                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "  returning to app");
1068                 // The gesture is returning to the pausing-task(s) rather than continuing with
1069                 // recents, so end the transition by moving the app back to the top (and also
1070                 // re-showing it's task).
1071                 for (int i = mPausingTasks.size() - 1; i >= 0; --i) {
1072                     // reverse order so that index 0 ends up on top
1073                     wct.reorder(mPausingTasks.get(i).mToken, true /* onTop */);
1074                     t.show(mPausingTasks.get(i).mTaskSurface);
1075                 }
1076                 if (!mKeyguardLocked && mRecentsTask != null) {
1077                     wct.restoreTransientOrder(mRecentsTask);
1078                 }
1079             } else if (toHome && mOpeningSeparateHome && mPausingTasks != null) {
1080                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "  3p launching home");
1081                 // Special situation where 3p launcher was changed during recents (this happens
1082                 // during tapltests...). Here we get both "return to home" AND "home opening".
1083                 // This is basically going home, but we have to restore the recents and home order.
1084                 for (int i = 0; i < mOpeningTasks.size(); ++i) {
1085                     final TaskState state = mOpeningTasks.get(i);
1086                     if (state.mTaskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
1087                         // Make sure it is on top.
1088                         wct.reorder(state.mToken, true /* onTop */);
1089                     }
1090                     t.show(state.mTaskSurface);
1091                 }
1092                 for (int i = mPausingTasks.size() - 1; i >= 0; --i) {
1093                     t.hide(mPausingTasks.get(i).mTaskSurface);
1094                 }
1095                 if (!mKeyguardLocked && mRecentsTask != null) {
1096                     wct.restoreTransientOrder(mRecentsTask);
1097                 }
1098             } else {
1099                 if (mPausingSeparateHome) {
1100                     if (mOpeningTasks.isEmpty()) {
1101                         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
1102                                 "  recents occluded 3p home");
1103                     } else {
1104                         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
1105                                 "  switch task by recents on 3p home");
1106                     }
1107                 }
1108                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "  normal finish");
1109                 // The general case: committing to recents, going home, or switching tasks.
1110                 for (int i = 0; i < mOpeningTasks.size(); ++i) {
1111                     t.show(mOpeningTasks.get(i).mTaskSurface);
1112                 }
1113                 for (int i = 0; i < mPausingTasks.size(); ++i) {
1114                     cleanUpPausingOrClosingTask(mPausingTasks.get(i), wct, t, sendUserLeaveHint);
1115                 }
1116                 for (int i = 0; i < mClosingTasks.size(); ++i) {
1117                     cleanUpPausingOrClosingTask(mClosingTasks.get(i), wct, t, sendUserLeaveHint);
1118                 }
1119                 if (mPipTransaction != null && sendUserLeaveHint) {
1120                     SurfaceControl pipLeash = null;
1121                     TransitionInfo.Change pipChange = null;
1122                     if (mPipTask != null) {
1123                         pipChange = mInfo.getChange(mPipTask);
1124                         pipLeash = pipChange.getLeash();
1125                     } else if (mPipTaskId != -1) {
1126                         // find a task with taskId from #setFinishTaskTransaction()
1127                         for (TransitionInfo.Change change : mInfo.getChanges()) {
1128                             if (change.getTaskInfo() != null
1129                                     && change.getTaskInfo().taskId == mPipTaskId) {
1130                                 pipChange = change;
1131                                 pipLeash = change.getLeash();
1132                                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
1133                                         "RecentsController.finishInner:"
1134                                                 + " found a change with taskId=%d", mPipTaskId);
1135                             }
1136                         }
1137                     }
1138                     if (pipLeash == null) {
1139                         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
1140                                 "RecentsController.finishInner: no valid PiP leash;"
1141                                         + "mPipTransaction=%s, mPipTask=%s, mPipTaskId=%d",
1142                                 mPipTransaction, mPipTask, mPipTaskId);
1143                     } else {
1144                         t.show(pipLeash);
1145                         PictureInPictureSurfaceTransaction.apply(mPipTransaction, pipLeash, t);
1146                         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
1147                                 "RecentsController.finishInner: PiP transaction %s merged",
1148                                 mPipTransaction);
1149                         if (PipUtils.isPip2ExperimentEnabled()) {
1150                             // If this path is triggered, we are in auto-enter PiP flow in gesture
1151                             // navigation mode, which means "Recents" transition should be followed
1152                             // by a TRANSIT_PIP. Hence, we take the WCT was about to be sent
1153                             // to Core to be applied during finishTransition(), we modify it to
1154                             // factor in PiP changes, and we send it as a direct startWCT for
1155                             // a new TRANSIT_PIP type transition. Recents still sends
1156                             // finishTransition() to update visibilities, but with finishWCT=null.
1157                             TransitionRequestInfo requestInfo = new TransitionRequestInfo(
1158                                     TRANSIT_PIP, null /* triggerTask */, pipChange.getTaskInfo(),
1159                                     null /* remote */, null /* displayChange */, 0 /* flags */);
1160                             // Use mTransition IBinder token temporarily just to get PipTransition
1161                             // to return from its handleRequest(). The actual TRANSIT_PIP will have
1162                             // anew token once it arrives into PipTransition#startAnimation().
1163                             Pair<Transitions.TransitionHandler, WindowContainerTransaction>
1164                                     requestRes = mTransitions.dispatchRequest(mTransition,
1165                                             requestInfo, null /* skip */);
1166                             wct.merge(requestRes.second, true);
1167                             mTransitions.startTransition(TRANSIT_PIP, wct, null /* handler */);
1168                             // We need to clear the WCT to send finishWCT=null for Recents.
1169                             wct.clear();
1170                         }
1171                     }
1172                     mPipTaskId = -1;
1173                     mPipTask = null;
1174                     mPipTransaction = null;
1175                 }
1176             }
1177             cleanUp();
1178             finishCB.onTransitionFinished(wct.isEmpty() ? null : wct);
1179             if (runnerFinishCb != null) {
1180                 try {
1181                     ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
1182                             "[%d] RecentsController.finishInner: calling finish callback",
1183                             mInstanceId);
1184                     runnerFinishCb.send(0, null);
1185                 } catch (RemoteException e) {
1186                     Slog.e(TAG, "Failed to report transition finished", e);
1187                 }
1188             }
1189         }
1190 
allAppsAreTranslucent(ArrayList<TaskState> tasks)1191         private boolean allAppsAreTranslucent(ArrayList<TaskState> tasks) {
1192             if (tasks == null) {
1193                 return false;
1194             }
1195             for (int i = tasks.size() - 1; i >= 0; --i) {
1196                 if (!tasks.get(i).mIsTranslucent) {
1197                     return false;
1198                 }
1199             }
1200             return true;
1201         }
1202 
createBackgroundSurface(SurfaceControl.Transaction transaction, SurfaceControl parent, int layer)1203         private void createBackgroundSurface(SurfaceControl.Transaction transaction,
1204                 SurfaceControl parent, int layer) {
1205             if (mBackgroundColor == null) {
1206                 return;
1207             }
1208             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
1209                     "  adding background color to layer=%d", layer);
1210             final SurfaceControl background = new SurfaceControl.Builder()
1211                     .setName("recents_background")
1212                     .setColorLayer()
1213                     .setOpaque(true)
1214                     .setParent(parent)
1215                     .build();
1216             transaction.setColor(background, colorToFloatArray(mBackgroundColor));
1217             transaction.setLayer(background, layer);
1218             transaction.setAlpha(background, 1F);
1219             transaction.show(background);
1220         }
1221 
colorToFloatArray(@onNull Color color)1222         private static float[] colorToFloatArray(@NonNull Color color) {
1223             return new float[]{color.red(), color.green(), color.blue()};
1224         }
1225 
cleanUpPausingOrClosingTask(TaskState task, WindowContainerTransaction wct, SurfaceControl.Transaction finishTransaction, boolean sendUserLeaveHint)1226         private void cleanUpPausingOrClosingTask(TaskState task, WindowContainerTransaction wct,
1227                 SurfaceControl.Transaction finishTransaction, boolean sendUserLeaveHint) {
1228             if (!sendUserLeaveHint && task.isLeaf()) {
1229                 // This means recents is not *actually* finishing, so of course we gotta
1230                 // do special stuff in WMCore to accommodate.
1231                 wct.setDoNotPip(task.mToken);
1232             }
1233             // Since we will reparent out of the leashes, pre-emptively hide the child
1234             // surface to match the leash. Otherwise, there will be a flicker before the
1235             // visibility gets committed in Core when using split-screen (in splitscreen,
1236             // the leaf-tasks are not "independent" so aren't hidden by normal setup).
1237             finishTransaction.hide(task.mTaskSurface);
1238         }
1239 
1240         @Override
setDeferCancelUntilNextTransition(boolean defer, boolean screenshot)1241         public void setDeferCancelUntilNextTransition(boolean defer, boolean screenshot) {
1242         }
1243 
1244         @Override
cleanupScreenshot()1245         public void cleanupScreenshot() {
1246         }
1247 
1248         @Override
setWillFinishToHome(boolean willFinishToHome)1249         public void setWillFinishToHome(boolean willFinishToHome) {
1250             mExecutor.execute(() -> {
1251                 mWillFinishToHome = willFinishToHome;
1252             });
1253         }
1254 
1255         /**
1256          * @see IRecentsAnimationController#removeTask
1257          */
1258         @Override
removeTask(int taskId)1259         public boolean removeTask(int taskId) {
1260             return false;
1261         }
1262 
1263         /**
1264          * @see IRecentsAnimationController#detachNavigationBarFromApp
1265          */
1266         @Override
detachNavigationBarFromApp(boolean moveHomeToTop)1267         public void detachNavigationBarFromApp(boolean moveHomeToTop) {
1268             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
1269                     "[%d] RecentsController.detachNavigationBarFromApp", mInstanceId);
1270             mExecutor.execute(() -> {
1271                 if (mTransition == null) return;
1272                 try {
1273                     ActivityTaskManager.getService().detachNavigationBarFromApp(mTransition);
1274                 } catch (RemoteException e) {
1275                     Slog.e(TAG, "Failed to detach the navigation bar from app", e);
1276                 }
1277             });
1278         }
1279 
1280         /**
1281          * @see IRecentsAnimationController#animateNavigationBarToApp(long)
1282          */
1283         @Override
animateNavigationBarToApp(long duration)1284         public void animateNavigationBarToApp(long duration) {
1285         }
1286     };
1287 
1288     /** Utility class to track the state of a task as-seen by recents. */
1289     private static class TaskState {
1290         WindowContainerToken mToken;
1291         ActivityManager.RunningTaskInfo mTaskInfo;
1292 
1293         /** The surface/leash of the task provided by Core. */
1294         SurfaceControl mTaskSurface;
1295 
1296         /** True when the task is translucent.  */
1297         final boolean mIsTranslucent;
1298 
1299         /** The (local) animation-leash created for this task. Only non-null for leafs. */
1300         @Nullable
1301         SurfaceControl mLeash;
1302 
TaskState(TransitionInfo.Change change, SurfaceControl leash)1303         TaskState(TransitionInfo.Change change, SurfaceControl leash) {
1304             mToken = change.getContainer();
1305             mTaskInfo = change.getTaskInfo();
1306             mTaskSurface = change.getLeash();
1307             mIsTranslucent = (change.getFlags() & FLAG_TRANSLUCENT) != 0;
1308             mLeash = leash;
1309         }
1310 
indexOf(ArrayList<TaskState> list, TransitionInfo.Change change)1311         static int indexOf(ArrayList<TaskState> list, TransitionInfo.Change change) {
1312             for (int i = list.size() - 1; i >= 0; --i) {
1313                 if (list.get(i).mToken.equals(change.getContainer())) {
1314                     return i;
1315                 }
1316             }
1317             return -1;
1318         }
1319 
isLeaf()1320         boolean isLeaf() {
1321             return mLeash != null;
1322         }
1323 
toString()1324         public String toString() {
1325             return "" + mToken + " : " + mLeash;
1326         }
1327     }
1328 
1329     /**
1330      * An interface for a mixed handler to receive information about recents requests (since these
1331      * come into this handler directly vs from WMCore request).
1332      */
1333     public interface RecentsMixedHandler extends Transitions.TransitionHandler {
1334         /**
1335          * Called when a recents request comes in. The handler can add operations to outWCT. If
1336          * the handler wants to "accept" the transition, it should return a Consumer accepting the
1337          * IBinder for the transition. If not, it should return `null`.
1338          *
1339          * If a mixed-handler accepts this recents, it will be the de-facto handler for this
1340          * transition and is required to call the associated {@link #startAnimation},
1341          * {@link #mergeAnimation}, and {@link #onTransitionConsumed} methods.
1342          */
1343         @Nullable
handleRecentsRequest(WindowContainerTransaction outWCT)1344         Consumer<IBinder> handleRecentsRequest(WindowContainerTransaction outWCT);
1345     }
1346 }
1347