1 /*
2  * Copyright (C) 2017 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.server.wm;
18 
19 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
20 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
21 import static android.view.RemoteAnimationTarget.MODE_CLOSING;
22 import static android.view.RemoteAnimationTarget.MODE_OPENING;
23 import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION;
24 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
25 
26 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
27 import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_RECENTS_ANIM;
28 import static com.android.server.wm.AnimationAdapterProto.REMOTE;
29 import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS;
30 import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET;
31 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
32 import static com.android.server.wm.WindowManagerInternal.AppTransitionListener;
33 
34 import android.annotation.IntDef;
35 import android.annotation.NonNull;
36 import android.app.ActivityManager.TaskSnapshot;
37 import android.app.WindowConfiguration;
38 import android.graphics.Point;
39 import android.graphics.Rect;
40 import android.os.Binder;
41 import android.os.IBinder.DeathRecipient;
42 import android.os.RemoteException;
43 import android.os.SystemClock;
44 import android.util.ArrayMap;
45 import android.util.ArraySet;
46 import android.util.IntArray;
47 import android.util.Slog;
48 import android.util.SparseBooleanArray;
49 import android.util.proto.ProtoOutputStream;
50 import android.view.IRecentsAnimationController;
51 import android.view.IRecentsAnimationRunner;
52 import android.view.InputWindowHandle;
53 import android.view.RemoteAnimationTarget;
54 import android.view.SurfaceControl;
55 import android.view.SurfaceControl.Transaction;
56 
57 import com.android.internal.annotations.VisibleForTesting;
58 import com.android.internal.inputmethod.SoftInputShowHideReason;
59 import com.android.internal.util.function.pooled.PooledConsumer;
60 import com.android.internal.util.function.pooled.PooledFunction;
61 import com.android.internal.util.function.pooled.PooledLambda;
62 import com.android.server.LocalServices;
63 import com.android.server.inputmethod.InputMethodManagerInternal;
64 import com.android.server.protolog.common.ProtoLog;
65 import com.android.server.statusbar.StatusBarManagerInternal;
66 import com.android.server.wm.SurfaceAnimator.AnimationType;
67 import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
68 import com.android.server.wm.utils.InsetUtils;
69 
70 import com.google.android.collect.Sets;
71 
72 import java.io.PrintWriter;
73 import java.util.ArrayList;
74 import java.util.stream.Collectors;
75 
76 /**
77  * Controls a single instance of the remote driven recents animation. In particular, this allows
78  * the calling SystemUI to animate the visible task windows as a part of the transition. The remote
79  * runner is provided an animation controller which allows it to take screenshots and to notify
80  * window manager when the animation is completed. In addition, window manager may also notify the
81  * app if it requires the animation to be canceled at any time (ie. due to timeout, etc.)
82  */
83 public class RecentsAnimationController implements DeathRecipient {
84     private static final String TAG = RecentsAnimationController.class.getSimpleName();
85     private static final long FAILSAFE_DELAY = 1000;
86 
87     public static final int REORDER_KEEP_IN_PLACE = 0;
88     public static final int REORDER_MOVE_TO_TOP = 1;
89     public static final int REORDER_MOVE_TO_ORIGINAL_POSITION = 2;
90 
91     @IntDef(prefix = { "REORDER_MODE_" }, value = {
92             REORDER_KEEP_IN_PLACE,
93             REORDER_MOVE_TO_TOP,
94             REORDER_MOVE_TO_ORIGINAL_POSITION
95     })
96     public @interface ReorderMode {}
97 
98     private final WindowManagerService mService;
99     private final StatusBarManagerInternal mStatusBar;
100     private IRecentsAnimationRunner mRunner;
101     private final RecentsAnimationCallbacks mCallbacks;
102     private final ArrayList<TaskAnimationAdapter> mPendingAnimations = new ArrayList<>();
103     private final IntArray mPendingNewTaskTargets = new IntArray(0);
104 
105     private final ArrayList<WallpaperAnimationAdapter> mPendingWallpaperAnimations =
106             new ArrayList<>();
107     private final int mDisplayId;
108     private boolean mWillFinishToHome = false;
109     private final Runnable mFailsafeRunnable = () -> cancelAnimation(
110             mWillFinishToHome ? REORDER_MOVE_TO_TOP : REORDER_MOVE_TO_ORIGINAL_POSITION,
111             "failSafeRunnable");
112 
113     // The recents component app token that is shown behind the visibile tasks
114     private ActivityRecord mTargetActivityRecord;
115     private DisplayContent mDisplayContent;
116     private int mTargetActivityType;
117     private Rect mMinimizedHomeBounds = new Rect();
118 
119     // We start the RecentsAnimationController in a pending-start state since we need to wait for
120     // the wallpaper/activity to draw before we can give control to the handler to start animating
121     // the visible task surfaces
122     private boolean mPendingStart = true;
123 
124     // Set when the animation has been canceled
125     private boolean mCanceled;
126 
127     // Whether or not the input consumer is enabled. The input consumer must be both registered and
128     // enabled for it to start intercepting touch events.
129     private boolean mInputConsumerEnabled;
130 
131     private final Rect mTmpRect = new Rect();
132 
133     private boolean mLinkedToDeathOfRunner;
134 
135     // Whether to try to defer canceling from a stack order change until the next transition
136     private boolean mRequestDeferCancelUntilNextTransition;
137     // Whether to actually defer canceling until the next transition
138     private boolean mCancelOnNextTransitionStart;
139     // Whether to take a screenshot when handling a deferred cancel
140     private boolean mCancelDeferredWithScreenshot;
141 
142     /**
143      * Animates the screenshot of task that used to be controlled by RecentsAnimation.
144      * @see {@link #setCancelOnNextTransitionStart}
145      */
146     SurfaceAnimator mRecentScreenshotAnimator;
147 
148     /**
149      * An app transition listener to cancel the recents animation only after the app transition
150      * starts or is canceled.
151      */
152     final AppTransitionListener mAppTransitionListener = new AppTransitionListener() {
153         @Override
154         public int onAppTransitionStartingLocked(int transit, long duration,
155                 long statusBarAnimationStartTime, long statusBarAnimationDuration) {
156             continueDeferredCancel();
157             return 0;
158         }
159 
160         @Override
161         public void onAppTransitionCancelledLocked(int transit) {
162             continueDeferredCancel();
163         }
164 
165         private void continueDeferredCancel() {
166             mDisplayContent.mAppTransition.unregisterListener(this);
167             if (mCanceled) {
168                 return;
169             }
170 
171             if (mCancelOnNextTransitionStart) {
172                 mCancelOnNextTransitionStart = false;
173                 cancelAnimationWithScreenshot(mCancelDeferredWithScreenshot);
174             }
175         }
176     };
177 
178     public interface RecentsAnimationCallbacks {
179         /** Callback when recents animation is finished. */
onAnimationFinished(@eorderMode int reorderMode, boolean sendUserLeaveHint)180         void onAnimationFinished(@ReorderMode int reorderMode, boolean sendUserLeaveHint);
181     }
182 
183     private final IRecentsAnimationController mController =
184             new IRecentsAnimationController.Stub() {
185 
186         @Override
187         public TaskSnapshot screenshotTask(int taskId) {
188             ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS,
189                     "screenshotTask(%d): mCanceled=%b", taskId, mCanceled);
190             final long token = Binder.clearCallingIdentity();
191             try {
192                 synchronized (mService.getWindowManagerLock()) {
193                     if (mCanceled) {
194                         return null;
195                     }
196                     for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
197                         final TaskAnimationAdapter adapter = mPendingAnimations.get(i);
198                         final Task task = adapter.mTask;
199                         if (task.mTaskId == taskId) {
200                             final TaskSnapshotController snapshotController =
201                                     mService.mTaskSnapshotController;
202                             final ArraySet<Task> tasks = Sets.newArraySet(task);
203                             snapshotController.snapshotTasks(tasks);
204                             snapshotController.addSkipClosingAppSnapshotTasks(tasks);
205                             return snapshotController.getSnapshot(taskId, 0 /* userId */,
206                                     false /* restoreFromDisk */, false /* isLowResolution */);
207                         }
208                     }
209                     return null;
210                 }
211             } finally {
212                 Binder.restoreCallingIdentity(token);
213             }
214         }
215 
216         @Override
217         public void finish(boolean moveHomeToTop, boolean sendUserLeaveHint) {
218             ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS,
219                     "finish(%b): mCanceled=%b", moveHomeToTop, mCanceled);
220             final long token = Binder.clearCallingIdentity();
221             try {
222                 synchronized (mService.getWindowManagerLock()) {
223                     if (mCanceled) {
224                         return;
225                     }
226                     // Remove all new task targets.
227                     for (int i = mPendingNewTaskTargets.size() - 1; i >= 0; i--) {
228                         removeTaskInternal(mPendingNewTaskTargets.get(i));
229                     }
230                 }
231 
232                 // Note, the callback will handle its own synchronization, do not lock on WM lock
233                 // prior to calling the callback
234                 mCallbacks.onAnimationFinished(moveHomeToTop
235                         ? REORDER_MOVE_TO_TOP
236                         : REORDER_MOVE_TO_ORIGINAL_POSITION, sendUserLeaveHint);
237             } finally {
238                 Binder.restoreCallingIdentity(token);
239             }
240         }
241 
242         @Override
243         public void setAnimationTargetsBehindSystemBars(boolean behindSystemBars)
244                 throws RemoteException {
245             final long token = Binder.clearCallingIdentity();
246             try {
247                 synchronized (mService.getWindowManagerLock()) {
248                     for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
249                         final Task task = mPendingAnimations.get(i).mTask;
250                         if (task.getActivityType() != mTargetActivityType) {
251                             task.setCanAffectSystemUiFlags(behindSystemBars);
252                         }
253                     }
254                     mService.mWindowPlacerLocked.requestTraversal();
255                 }
256             } finally {
257                 Binder.restoreCallingIdentity(token);
258             }
259         }
260 
261         @Override
262         public void setInputConsumerEnabled(boolean enabled) {
263             ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS,
264                     "setInputConsumerEnabled(%s): mCanceled=%b", enabled, mCanceled);
265             final long token = Binder.clearCallingIdentity();
266             try {
267                 synchronized (mService.getWindowManagerLock()) {
268                     if (mCanceled) {
269                         return;
270                     }
271 
272                     mInputConsumerEnabled = enabled;
273                     final InputMonitor inputMonitor = mDisplayContent.getInputMonitor();
274                     inputMonitor.updateInputWindowsLw(true /*force*/);
275                     mService.scheduleAnimationLocked();
276                 }
277             } finally {
278                 Binder.restoreCallingIdentity(token);
279             }
280         }
281 
282         @Override
283         public void hideCurrentInputMethod() {
284             final long token = Binder.clearCallingIdentity();
285             try {
286                 final InputMethodManagerInternal inputMethodManagerInternal =
287                         LocalServices.getService(InputMethodManagerInternal.class);
288                 if (inputMethodManagerInternal != null) {
289                     inputMethodManagerInternal.hideCurrentInputMethod(
290                             SoftInputShowHideReason.HIDE_RECENTS_ANIMATION);
291                 }
292             } finally {
293                 Binder.restoreCallingIdentity(token);
294             }
295         }
296 
297         @Override
298         public void setDeferCancelUntilNextTransition(boolean defer, boolean screenshot) {
299             synchronized (mService.mGlobalLock) {
300                 setDeferredCancel(defer, screenshot);
301             }
302         }
303 
304         @Override
305         public void cleanupScreenshot() {
306             synchronized (mService.mGlobalLock) {
307                 if (mRecentScreenshotAnimator != null) {
308                     mRecentScreenshotAnimator.cancelAnimation();
309                     mRecentScreenshotAnimator = null;
310                 }
311             }
312         }
313 
314         @Override
315         public void setWillFinishToHome(boolean willFinishToHome) {
316             synchronized (mService.getWindowManagerLock()) {
317                 mWillFinishToHome = willFinishToHome;
318             }
319         }
320 
321         @Override
322         public boolean removeTask(int taskId) {
323             final long token = Binder.clearCallingIdentity();
324             try {
325                 synchronized (mService.getWindowManagerLock()) {
326                     return removeTaskInternal(taskId);
327                 }
328             } finally {
329                 Binder.restoreCallingIdentity(token);
330             }
331         }
332     };
333 
334     /**
335      * @param remoteAnimationRunner The remote runner which should be notified when the animation is
336      *                              ready to start or has been canceled
337      * @param callbacks Callbacks to be made when the animation finishes
338      */
RecentsAnimationController(WindowManagerService service, IRecentsAnimationRunner remoteAnimationRunner, RecentsAnimationCallbacks callbacks, int displayId)339     RecentsAnimationController(WindowManagerService service,
340             IRecentsAnimationRunner remoteAnimationRunner, RecentsAnimationCallbacks callbacks,
341             int displayId) {
342         mService = service;
343         mRunner = remoteAnimationRunner;
344         mCallbacks = callbacks;
345         mDisplayId = displayId;
346         mStatusBar = LocalServices.getService(StatusBarManagerInternal.class);
347         mDisplayContent = service.mRoot.getDisplayContent(displayId);
348     }
349 
350     /**
351      * Initializes the recents animation controller. This is a separate call from the constructor
352      * because it may call cancelAnimation() which needs to properly clean up the controller
353      * in the window manager.
354      */
initialize(int targetActivityType, SparseBooleanArray recentTaskIds, ActivityRecord targetActivity)355     public void initialize(int targetActivityType, SparseBooleanArray recentTaskIds,
356             ActivityRecord targetActivity) {
357         mTargetActivityType = targetActivityType;
358         mDisplayContent.mAppTransition.registerListenerLocked(mAppTransitionListener);
359 
360         // Make leashes for each of the visible/target tasks and add it to the recents animation to
361         // be started
362         // TODO(b/153090560): Support Recents on multiple task display areas
363         final ArrayList<Task> visibleTasks = mDisplayContent.getDefaultTaskDisplayArea()
364                 .getVisibleTasks();
365         final ActivityStack targetStack = mDisplayContent.getDefaultTaskDisplayArea()
366                 .getStack(WINDOWING_MODE_UNDEFINED, targetActivityType);
367         if (targetStack != null) {
368             final PooledConsumer c = PooledLambda.obtainConsumer((t, outList) ->
369 	            { if (!outList.contains(t)) outList.add(t); }, PooledLambda.__(Task.class),
370                     visibleTasks);
371             targetStack.forAllLeafTasks(c, true /* traverseTopToBottom */);
372             c.recycle();
373         }
374 
375         final int taskCount = visibleTasks.size();
376         for (int i = 0; i < taskCount; i++) {
377             final Task task = visibleTasks.get(i);
378             final WindowConfiguration config = task.getWindowConfiguration();
379             if (config.tasksAreFloating()
380                     || config.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
381                 continue;
382             }
383             addAnimation(task, !recentTaskIds.get(task.mTaskId));
384         }
385 
386         // Skip the animation if there is nothing to animate
387         if (mPendingAnimations.isEmpty()) {
388             cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "initialize-noVisibleTasks");
389             return;
390         }
391 
392         try {
393             linkToDeathOfRunner();
394         } catch (RemoteException e) {
395             cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "initialize-failedToLinkToDeath");
396             return;
397         }
398 
399         // Adjust the wallpaper visibility for the showing target activity
400         ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS,
401                 "setHomeApp(%s)", targetActivity.getName());
402         mTargetActivityRecord = targetActivity;
403         if (targetActivity.windowsCanBeWallpaperTarget()) {
404             mDisplayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
405             mDisplayContent.setLayoutNeeded();
406         }
407 
408         // Save the minimized home height
409         final ActivityStack rootHomeTask =
410                 mDisplayContent.getDefaultTaskDisplayArea().getRootHomeTask();
411         mMinimizedHomeBounds = rootHomeTask != null ? rootHomeTask.getBounds() : null;
412 
413         mService.mWindowPlacerLocked.performSurfacePlacement();
414 
415         mDisplayContent.mFixedRotationTransitionListener.onStartRecentsAnimation(targetActivity);
416 
417         // Notify that the animation has started
418         if (mStatusBar != null) {
419             mStatusBar.onRecentsAnimationStateChanged(true /* running */);
420         }
421     }
422 
423     @VisibleForTesting
addAnimation(Task task, boolean isRecentTaskInvisible)424     AnimationAdapter addAnimation(Task task, boolean isRecentTaskInvisible) {
425         return addAnimation(task, isRecentTaskInvisible, false /* hidden */,
426                 null /* finishedCallback */);
427     }
428 
429     @VisibleForTesting
addAnimation(Task task, boolean isRecentTaskInvisible, boolean hidden, OnAnimationFinishedCallback finishedCallback)430     AnimationAdapter addAnimation(Task task, boolean isRecentTaskInvisible, boolean hidden,
431             OnAnimationFinishedCallback finishedCallback) {
432         ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "addAnimation(%s)", task.getName());
433         final TaskAnimationAdapter taskAdapter = new TaskAnimationAdapter(task,
434                 isRecentTaskInvisible);
435         task.startAnimation(task.getPendingTransaction(), taskAdapter, hidden,
436                 ANIMATION_TYPE_RECENTS, finishedCallback);
437         task.commitPendingTransaction();
438         mPendingAnimations.add(taskAdapter);
439         return taskAdapter;
440     }
441 
442     @VisibleForTesting
removeAnimation(TaskAnimationAdapter taskAdapter)443     void removeAnimation(TaskAnimationAdapter taskAdapter) {
444         ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS,
445                 "removeAnimation(%d)", taskAdapter.mTask.mTaskId);
446         taskAdapter.mTask.setCanAffectSystemUiFlags(true);
447         taskAdapter.mCapturedFinishCallback.onAnimationFinished(taskAdapter.mLastAnimationType,
448                 taskAdapter);
449         mPendingAnimations.remove(taskAdapter);
450     }
451 
452     @VisibleForTesting
removeWallpaperAnimation(WallpaperAnimationAdapter wallpaperAdapter)453     void removeWallpaperAnimation(WallpaperAnimationAdapter wallpaperAdapter) {
454         ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "removeWallpaperAnimation()");
455         wallpaperAdapter.getLeashFinishedCallback().onAnimationFinished(
456                 wallpaperAdapter.getLastAnimationType(), wallpaperAdapter);
457         mPendingWallpaperAnimations.remove(wallpaperAdapter);
458     }
459 
startAnimation()460     void startAnimation() {
461         ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS,
462                 "startAnimation(): mPendingStart=%b mCanceled=%b", mPendingStart, mCanceled);
463         if (!mPendingStart || mCanceled) {
464             // Skip starting if we've already started or canceled the animation
465             return;
466         }
467         try {
468             // Create the app targets
469             final RemoteAnimationTarget[] appTargets = createAppAnimations();
470 
471             // Skip the animation if there is nothing to animate
472             if (appTargets.length == 0) {
473                 cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "startAnimation-noAppWindows");
474                 return;
475             }
476 
477             // Create the wallpaper targets
478             final RemoteAnimationTarget[] wallpaperTargets = createWallpaperAnimations();
479 
480             mPendingStart = false;
481 
482             // Perform layout if it was scheduled before to make sure that we get correct content
483             // insets for the target app window after a rotation
484             mDisplayContent.performLayout(false /* initial */, false /* updateInputWindows */);
485 
486             final Rect minimizedHomeBounds = mTargetActivityRecord != null
487                     && mTargetActivityRecord.inSplitScreenSecondaryWindowingMode()
488                             ? mMinimizedHomeBounds
489                             : null;
490             final Rect contentInsets;
491             if (mTargetActivityRecord != null && mTargetActivityRecord.findMainWindow() != null) {
492                 contentInsets = mTargetActivityRecord.findMainWindow().getContentInsets();
493             } else {
494                 // If the window for the activity had not yet been created, use the display insets.
495                 mService.getStableInsets(mDisplayId, mTmpRect);
496                 contentInsets = mTmpRect;
497             }
498             mRunner.onAnimationStart(mController, appTargets, wallpaperTargets, contentInsets,
499                     minimizedHomeBounds);
500             ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS,
501                     "startAnimation(): Notify animation start: %s",
502                     mPendingAnimations.stream()
503                             .map(anim->anim.mTask.mTaskId).collect(Collectors.toList()));
504         } catch (RemoteException e) {
505             Slog.e(TAG, "Failed to start recents animation", e);
506         }
507 
508         if (mTargetActivityRecord != null) {
509             final ArrayMap<WindowContainer, Integer> reasons = new ArrayMap<>(1);
510             reasons.put(mTargetActivityRecord, APP_TRANSITION_RECENTS_ANIM);
511             mService.mAtmService.mStackSupervisor.getActivityMetricsLogger()
512                     .notifyTransitionStarting(reasons);
513         }
514     }
515 
addTaskToTargets(Task task, OnAnimationFinishedCallback finishedCallback)516     void addTaskToTargets(Task task, OnAnimationFinishedCallback finishedCallback) {
517         if (mRunner != null) {
518             // No need to send task appeared when the task target already exists.
519             if (isAnimatingTask(task)) {
520                 return;
521             }
522             final RemoteAnimationTarget target = createTaskRemoteAnimation(task, finishedCallback);
523             if (target == null) {
524                 return;
525             }
526             ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "addTaskToTargets, target: %s", target);
527             try {
528                 mRunner.onTaskAppeared(target);
529             } catch (RemoteException e) {
530                 Slog.e(TAG, "Failed to report task appeared", e);
531             }
532         }
533     }
534 
createTaskRemoteAnimation(Task task, OnAnimationFinishedCallback finishedCallback)535     private RemoteAnimationTarget createTaskRemoteAnimation(Task task,
536             OnAnimationFinishedCallback finishedCallback) {
537         final SparseBooleanArray recentTaskIds =
538                 mService.mAtmService.getRecentTasks().getRecentTaskIds();
539         TaskAnimationAdapter adapter = (TaskAnimationAdapter) addAnimation(task,
540                 !recentTaskIds.get(task.mTaskId), true /* hidden */, finishedCallback);
541         mPendingNewTaskTargets.add(task.mTaskId);
542         return adapter.createRemoteAnimationTarget();
543     }
544 
removeTaskInternal(int taskId)545     private boolean removeTaskInternal(int taskId) {
546         boolean result = false;
547         for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
548             // Only allows when task target has became visible to user, to prevent
549             // the flickering during remove animation and task visible.
550             final TaskAnimationAdapter target = mPendingAnimations.get(i);
551             if (target.mTask.mTaskId == taskId && target.mTask.isOnTop()) {
552                 removeAnimation(target);
553                 final int taskIndex = mPendingNewTaskTargets.indexOf(taskId);
554                 if (taskIndex != -1) {
555                     mPendingNewTaskTargets.remove(taskIndex);
556                 }
557                 result = true;
558                 break;
559             }
560         }
561         return result;
562     }
563 
createAppAnimations()564     private RemoteAnimationTarget[] createAppAnimations() {
565         final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>();
566         for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
567             final TaskAnimationAdapter taskAdapter = mPendingAnimations.get(i);
568             final RemoteAnimationTarget target = taskAdapter.createRemoteAnimationTarget();
569             if (target != null) {
570                 targets.add(target);
571             } else {
572                 removeAnimation(taskAdapter);
573             }
574         }
575         return targets.toArray(new RemoteAnimationTarget[targets.size()]);
576     }
577 
createWallpaperAnimations()578     private RemoteAnimationTarget[] createWallpaperAnimations() {
579         ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "createWallpaperAnimations()");
580         return WallpaperAnimationAdapter.startWallpaperAnimations(mService, 0L, 0L,
581                 adapter -> {
582                     synchronized (mService.mGlobalLock) {
583                         // If the wallpaper animation is canceled, continue with the recents
584                         // animation
585                         mPendingWallpaperAnimations.remove(adapter);
586                     }
587                 }, mPendingWallpaperAnimations);
588     }
589 
590     void cancelAnimation(@ReorderMode int reorderMode, String reason) {
591         cancelAnimation(reorderMode, false /*screenshot */, reason);
592     }
593 
594     void cancelAnimationWithScreenshot(boolean screenshot) {
595         cancelAnimation(REORDER_KEEP_IN_PLACE, screenshot, "stackOrderChanged");
596     }
597 
598     private void cancelAnimation(@ReorderMode int reorderMode, boolean screenshot, String reason) {
599         ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "cancelAnimation(): reason=%s", reason);
600         synchronized (mService.getWindowManagerLock()) {
601             if (mCanceled) {
602                 // We've already canceled the animation
603                 return;
604             }
605             mService.mH.removeCallbacks(mFailsafeRunnable);
606             mCanceled = true;
607 
608             if (screenshot) {
609                 // Screen shot previous task when next task starts transition and notify the runner.
610                 // We will actually finish the animation once the runner calls cleanUpScreenshot().
611                 final Task task = mPendingAnimations.get(0).mTask;
612                 final TaskSnapshot taskSnapshot = screenshotRecentTask(task, reorderMode);
613                 try {
614                     mRunner.onAnimationCanceled(taskSnapshot);
615                 } catch (RemoteException e) {
616                     Slog.e(TAG, "Failed to cancel recents animation", e);
617                 }
618                 if (taskSnapshot == null) {
619                     mCallbacks.onAnimationFinished(reorderMode, false /* sendUserLeaveHint */);
620                 }
621             } else {
622                 // Otherwise, notify the runner and clean up the animation immediately
623                 // Note: In the fallback case, this can trigger multiple onAnimationCancel() calls
624                 // to the runner if we this actually triggers cancel twice on the caller
625                 try {
626                     mRunner.onAnimationCanceled(null /* taskSnapshot */);
627                 } catch (RemoteException e) {
628                     Slog.e(TAG, "Failed to cancel recents animation", e);
629                 }
630                 mCallbacks.onAnimationFinished(reorderMode, false /* sendUserLeaveHint */);
631             }
632         }
633     }
634 
635     /**
636      * Cancel recents animation when the next app transition starts.
637      * <p>
638      * When we cancel the recents animation due to a stack order change, we can't just cancel it
639      * immediately as it would lead to a flicker in Launcher if we just remove the task from the
640      * leash. Instead we screenshot the previous task and replace the child of the leash with the
641      * screenshot, so that Launcher can still control the leash lifecycle & make the next app
642      * transition animate smoothly without flickering.
643      */
644     void setCancelOnNextTransitionStart() {
645         mCancelOnNextTransitionStart = true;
646     }
647 
648     /**
649      * Requests that we attempt to defer the cancel until the next app transition if we are
650      * canceling from a stack order change.  If {@param screenshot} is specified, then the system
651      * will replace the contents of the leash with a screenshot, which must be cleaned up when the
652      * runner calls cleanUpScreenshot().
653      */
654     void setDeferredCancel(boolean defer, boolean screenshot) {
655         mRequestDeferCancelUntilNextTransition = defer;
656         mCancelDeferredWithScreenshot = screenshot;
657     }
658 
659     /**
660      * @return Whether we should defer the cancel from a stack order change until the next app
661      * transition.
662      */
663     boolean shouldDeferCancelUntilNextTransition() {
664         return mRequestDeferCancelUntilNextTransition;
665     }
666 
667     /**
668      * @return Whether we should both defer the cancel from a stack order change until the next
669      * app transition, and also that the deferred cancel should replace the contents of the leash
670      * with a screenshot.
671      */
672     boolean shouldDeferCancelWithScreenshot() {
673         return mRequestDeferCancelUntilNextTransition && mCancelDeferredWithScreenshot;
674     }
675 
676     TaskSnapshot screenshotRecentTask(Task task, @ReorderMode int reorderMode) {
677         final TaskSnapshotController snapshotController = mService.mTaskSnapshotController;
678         final ArraySet<Task> tasks = Sets.newArraySet(task);
679         snapshotController.snapshotTasks(tasks);
680         snapshotController.addSkipClosingAppSnapshotTasks(tasks);
681         final TaskSnapshot taskSnapshot = snapshotController.getSnapshot(task.mTaskId,
682                 task.mUserId, false /* restoreFromDisk */, false /* isLowResolution */);
683         if (taskSnapshot == null) {
684             return null;
685         }
686 
687         final TaskScreenshotAnimatable animatable = new TaskScreenshotAnimatable(mService.mSurfaceControlFactory, task,
688                 new SurfaceControl.ScreenshotGraphicBuffer(taskSnapshot.getSnapshot(),
689                         taskSnapshot.getColorSpace(), false /* containsSecureLayers */));
690         mRecentScreenshotAnimator = new SurfaceAnimator(
691                 animatable,
692                 (type, anim) -> {
693                     ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "mRecentScreenshotAnimator finish");
694                     mCallbacks.onAnimationFinished(reorderMode, false /* sendUserLeaveHint */);
695                 }, mService);
696         mRecentScreenshotAnimator.transferAnimation(task.mSurfaceAnimator);
697         return taskSnapshot;
698     }
699 
700     void cleanupAnimation(@ReorderMode int reorderMode) {
701         ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS,
702                         "cleanupAnimation(): Notify animation finished mPendingAnimations=%d "
703                                 + "reorderMode=%d",
704                         mPendingAnimations.size(), reorderMode);
705         for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
706             final TaskAnimationAdapter taskAdapter = mPendingAnimations.get(i);
707             if (reorderMode == REORDER_MOVE_TO_TOP || reorderMode == REORDER_KEEP_IN_PLACE) {
708                 taskAdapter.mTask.dontAnimateDimExit();
709             }
710             removeAnimation(taskAdapter);
711         }
712 
713         for (int i = mPendingWallpaperAnimations.size() - 1; i >= 0; i--) {
714             final WallpaperAnimationAdapter wallpaperAdapter = mPendingWallpaperAnimations.get(i);
715             removeWallpaperAnimation(wallpaperAdapter);
716         }
717 
718         // Clear any pending failsafe runnables
719         mService.mH.removeCallbacks(mFailsafeRunnable);
720         mDisplayContent.mAppTransition.unregisterListener(mAppTransitionListener);
721 
722         // Clear references to the runner
723         unlinkToDeathOfRunner();
724         mRunner = null;
725         mCanceled = true;
726 
727         // Make sure previous animator has cleaned-up.
728         if (mRecentScreenshotAnimator != null) {
729             mRecentScreenshotAnimator.cancelAnimation();
730             mRecentScreenshotAnimator = null;
731         }
732 
733         // Update the input windows after the animation is complete
734         final InputMonitor inputMonitor = mDisplayContent.getInputMonitor();
735         inputMonitor.updateInputWindowsLw(true /*force*/);
736 
737         // We have deferred all notifications to the target app as a part of the recents animation,
738         // so if we are actually transitioning there, notify again here
739         if (mTargetActivityRecord != null) {
740             if (reorderMode == REORDER_MOVE_TO_TOP || reorderMode == REORDER_KEEP_IN_PLACE) {
741                 mDisplayContent.mAppTransition.notifyAppTransitionFinishedLocked(
742                         mTargetActivityRecord.token);
743             }
744         }
745         mDisplayContent.mFixedRotationTransitionListener.onFinishRecentsAnimation(
746                 reorderMode == REORDER_MOVE_TO_ORIGINAL_POSITION /* moveRecentsToBack */);
747 
748         // Notify that the animation has ended
749         if (mStatusBar != null) {
750             mStatusBar.onRecentsAnimationStateChanged(false /* running */);
751         }
752     }
753 
754     void scheduleFailsafe() {
755         mService.mH.postDelayed(mFailsafeRunnable, FAILSAFE_DELAY);
756     }
757 
758     private void linkToDeathOfRunner() throws RemoteException {
759         if (!mLinkedToDeathOfRunner) {
760             mRunner.asBinder().linkToDeath(this, 0);
761             mLinkedToDeathOfRunner = true;
762         }
763     }
764 
765     private void unlinkToDeathOfRunner() {
766         if (mLinkedToDeathOfRunner) {
767             mRunner.asBinder().unlinkToDeath(this, 0);
768             mLinkedToDeathOfRunner = false;
769         }
770     }
771 
772     @Override
773     public void binderDied() {
774         cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "binderDied");
775 
776         synchronized (mService.getWindowManagerLock()) {
777             // Clear associated input consumers on runner death
778             final InputMonitor inputMonitor = mDisplayContent.getInputMonitor();
779             inputMonitor.destroyInputConsumer(INPUT_CONSUMER_RECENTS_ANIMATION);
780         }
781     }
782 
783     void checkAnimationReady(WallpaperController wallpaperController) {
784         if (mPendingStart) {
785             final boolean wallpaperReady = !isTargetOverWallpaper()
786                     || (wallpaperController.getWallpaperTarget() != null
787                             && wallpaperController.wallpaperTransitionReady());
788             if (wallpaperReady) {
789                 mService.getRecentsAnimationController().startAnimation();
790             }
791         }
792     }
793 
794     boolean isWallpaperVisible(WindowState w) {
795         return w != null && w.mAttrs.type == TYPE_BASE_APPLICATION &&
796                 ((w.mActivityRecord != null && mTargetActivityRecord == w.mActivityRecord)
797                         || isAnimatingTask(w.getTask()))
798                 && isTargetOverWallpaper();
799     }
800 
801     /**
802      * @return Whether to use the input consumer to override app input to route home/recents.
803      */
804     boolean shouldApplyInputConsumer(ActivityRecord activity) {
805         // Only apply the input consumer if it is enabled, it is not the target (home/recents)
806         // being revealed with the transition, and we are actively animating the app as a part of
807         // the animation
808         return mInputConsumerEnabled && activity != null
809                 && !isTargetApp(activity) && isAnimatingApp(activity);
810     }
811 
812     boolean updateInputConsumerForApp(InputWindowHandle inputWindowHandle,
813             boolean hasFocus) {
814         // Update the input consumer touchable region to match the target app main window
815         final WindowState targetAppMainWindow = mTargetActivityRecord != null
816                 ? mTargetActivityRecord.findMainWindow()
817                 : null;
818         if (targetAppMainWindow != null) {
819             targetAppMainWindow.getBounds(mTmpRect);
820             inputWindowHandle.hasFocus = hasFocus;
821             inputWindowHandle.touchableRegion.set(mTmpRect);
822             return true;
823         }
824         return false;
825     }
826 
827     boolean isTargetApp(ActivityRecord activity) {
828         return mTargetActivityRecord != null && activity == mTargetActivityRecord;
829     }
830 
831     private boolean isTargetOverWallpaper() {
832         if (mTargetActivityRecord == null) {
833             return false;
834         }
835         return mTargetActivityRecord.windowsCanBeWallpaperTarget();
836     }
837 
838     boolean isAnimatingTask(Task task) {
839         for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
840             if (task == mPendingAnimations.get(i).mTask) {
841                 return true;
842             }
843         }
844         return false;
845     }
846 
847     boolean isAnimatingWallpaper(WallpaperWindowToken token) {
848         for (int i = mPendingWallpaperAnimations.size() - 1; i >= 0; i--) {
849             if (token == mPendingWallpaperAnimations.get(i).getToken()) {
850                 return true;
851             }
852         }
853         return false;
854     }
855 
856     private boolean isAnimatingApp(ActivityRecord activity) {
857         for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
858             final Task task = mPendingAnimations.get(i).mTask;
859             final PooledFunction f = PooledLambda.obtainFunction(
860                     (a, b) -> a == b, activity,
861                     PooledLambda.__(ActivityRecord.class));
862             boolean isAnimatingApp = task.forAllActivities(f);
863             f.recycle();
864             if (isAnimatingApp) {
865                 return true;
866             }
867         }
868         return false;
869     }
870 
871     boolean shouldIgnoreForAccessibility(WindowState windowState) {
872         final Task task = windowState.getTask();
873         return task != null && isAnimatingTask(task) && !isTargetApp(windowState.mActivityRecord);
874     }
875 
876     /**
877      * If the animation target ActivityRecord has a fixed rotation ({@link
878      * WindowToken#hasFixedRotationTransform()}, the provided wallpaper will be rotated accordingly.
879      *
880      * This avoids any screen rotation animation when animating to the Recents view.
881      */
882     void linkFixedRotationTransformIfNeeded(@NonNull WindowToken wallpaper) {
883         if (mTargetActivityRecord == null) {
884             return;
885         }
886         wallpaper.linkFixedRotationTransform(mTargetActivityRecord);
887     }
888 
889     @VisibleForTesting
890     class TaskAnimationAdapter implements AnimationAdapter {
891 
892         private final Task mTask;
893         private SurfaceControl mCapturedLeash;
894         private OnAnimationFinishedCallback mCapturedFinishCallback;
895         private @AnimationType int mLastAnimationType;
896         private final boolean mIsRecentTaskInvisible;
897         private RemoteAnimationTarget mTarget;
898         private final Rect mBounds = new Rect();
899         // The bounds of the target relative to its parent.
900         private Rect mLocalBounds = new Rect();
901 
902         TaskAnimationAdapter(Task task, boolean isRecentTaskInvisible) {
903             mTask = task;
904             mIsRecentTaskInvisible = isRecentTaskInvisible;
905             mBounds.set(mTask.getBounds());
906 
907             mLocalBounds.set(mBounds);
908             Point tmpPos = new Point();
909             mTask.getRelativePosition(tmpPos);
910             mLocalBounds.offsetTo(tmpPos.x, tmpPos.y);
911         }
912 
913         RemoteAnimationTarget createRemoteAnimationTarget() {
914             final ActivityRecord topApp = mTask.getTopVisibleActivity();
915             final WindowState mainWindow = topApp != null
916                     ? topApp.findMainWindow()
917                     : null;
918             if (mainWindow == null) {
919                 return null;
920             }
921             final Rect insets = new Rect();
922             mainWindow.getContentInsets(insets);
923             InsetUtils.addInsets(insets, mainWindow.mActivityRecord.getLetterboxInsets());
924             final int mode = topApp.getActivityType() == mTargetActivityType
925                     ? MODE_OPENING
926                     : MODE_CLOSING;
927             mTarget = new RemoteAnimationTarget(mTask.mTaskId, mode, mCapturedLeash,
928                     !topApp.fillsParent(), mainWindow.mWinAnimator.mLastClipRect,
929                     insets, mTask.getPrefixOrderIndex(), new Point(mBounds.left, mBounds.top),
930                     mLocalBounds, mBounds, mTask.getWindowConfiguration(),
931                     mIsRecentTaskInvisible, null, null);
932             return mTarget;
933         }
934 
935         @Override
936         public boolean getShowWallpaper() {
937             return false;
938         }
939 
940         @Override
941         public void startAnimation(SurfaceControl animationLeash, Transaction t,
942                 @AnimationType int type, OnAnimationFinishedCallback finishCallback) {
943             // Restore position and stack crop until client has a chance to modify it.
944             t.setPosition(animationLeash, mLocalBounds.left, mLocalBounds.top);
945             mTmpRect.set(mLocalBounds);
946             mTmpRect.offsetTo(0, 0);
947             t.setWindowCrop(animationLeash, mTmpRect);
948             mCapturedLeash = animationLeash;
949             mCapturedFinishCallback = finishCallback;
950             mLastAnimationType = type;
951         }
952 
953         @Override
954         public void onAnimationCancelled(SurfaceControl animationLeash) {
955             // Cancel the animation immediately if any single task animator is canceled
956             cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "taskAnimationAdapterCanceled");
957         }
958 
959         @Override
960         public long getDurationHint() {
961             return 0;
962         }
963 
964         @Override
965         public long getStatusBarTransitionsStartTime() {
966             return SystemClock.uptimeMillis();
967         }
968 
969         @Override
970         public void dump(PrintWriter pw, String prefix) {
971             pw.print(prefix); pw.println("task=" + mTask);
972             if (mTarget != null) {
973                 pw.print(prefix); pw.println("Target:");
974                 mTarget.dump(pw, prefix + "  ");
975             } else {
976                 pw.print(prefix); pw.println("Target: null");
977             }
978             pw.println("mIsRecentTaskInvisible=" + mIsRecentTaskInvisible);
979             pw.println("mLocalBounds=" + mLocalBounds);
980             pw.println("mBounds=" + mBounds);
981             pw.println("mIsRecentTaskInvisible=" + mIsRecentTaskInvisible);
982         }
983 
984         @Override
985         public void dumpDebug(ProtoOutputStream proto) {
986             final long token = proto.start(REMOTE);
987             if (mTarget != null) {
988                 mTarget.dumpDebug(proto, TARGET);
989             }
990             proto.end(token);
991         }
992     }
993 
994     public void dump(PrintWriter pw, String prefix) {
995         final String innerPrefix = prefix + "  ";
996         pw.print(prefix); pw.println(RecentsAnimationController.class.getSimpleName() + ":");
997         pw.print(innerPrefix); pw.println("mPendingStart=" + mPendingStart);
998         pw.print(innerPrefix); pw.println("mPendingAnimations=" + mPendingAnimations.size());
999         pw.print(innerPrefix); pw.println("mCanceled=" + mCanceled);
1000         pw.print(innerPrefix); pw.println("mInputConsumerEnabled=" + mInputConsumerEnabled);
1001         pw.print(innerPrefix); pw.println("mTargetActivityRecord=" + mTargetActivityRecord);
1002         pw.print(innerPrefix); pw.println("isTargetOverWallpaper=" + isTargetOverWallpaper());
1003         pw.print(innerPrefix); pw.println("mRequestDeferCancelUntilNextTransition="
1004                 + mRequestDeferCancelUntilNextTransition);
1005         pw.print(innerPrefix); pw.println("mCancelOnNextTransitionStart="
1006                 + mCancelOnNextTransitionStart);
1007         pw.print(innerPrefix); pw.println("mCancelDeferredWithScreenshot="
1008                 + mCancelDeferredWithScreenshot);
1009     }
1010 }
1011