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