• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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_FULLSCREEN;
20 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
21 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
22 import static android.view.RemoteAnimationTarget.MODE_CLOSING;
23 import static android.view.RemoteAnimationTarget.MODE_OPENING;
24 import static android.view.WindowManager.DOCKED_INVALID;
25 import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION;
26 
27 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
28 import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_RECENTS_ANIM;
29 import static com.android.server.wm.AnimationAdapterProto.REMOTE;
30 import static com.android.server.wm.BoundsAnimationController.FADE_IN;
31 import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET;
32 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_RECENTS_ANIMATIONS;
33 import static com.android.server.wm.WindowManagerInternal.AppTransitionListener;
34 
35 import android.annotation.IntDef;
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.ArraySet;
45 import android.util.Slog;
46 import android.util.SparseBooleanArray;
47 import android.util.SparseIntArray;
48 import android.util.proto.ProtoOutputStream;
49 import android.view.IRecentsAnimationController;
50 import android.view.IRecentsAnimationRunner;
51 import android.view.InputWindowHandle;
52 import android.view.RemoteAnimationTarget;
53 import android.view.SurfaceControl;
54 import android.view.SurfaceControl.Transaction;
55 
56 import com.android.internal.annotations.VisibleForTesting;
57 import com.android.server.LocalServices;
58 import com.android.server.inputmethod.InputMethodManagerInternal;
59 import com.android.server.statusbar.StatusBarManagerInternal;
60 import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
61 import com.android.server.wm.utils.InsetUtils;
62 
63 import com.google.android.collect.Sets;
64 
65 import java.io.PrintWriter;
66 import java.util.ArrayList;
67 
68 /**
69  * Controls a single instance of the remote driven recents animation. In particular, this allows
70  * the calling SystemUI to animate the visible task windows as a part of the transition. The remote
71  * runner is provided an animation controller which allows it to take screenshots and to notify
72  * window manager when the animation is completed. In addition, window manager may also notify the
73  * app if it requires the animation to be canceled at any time (ie. due to timeout, etc.)
74  */
75 public class RecentsAnimationController implements DeathRecipient {
76     private static final String TAG = RecentsAnimationController.class.getSimpleName();
77     private static final long FAILSAFE_DELAY = 1000;
78 
79     public static final int REORDER_KEEP_IN_PLACE = 0;
80     public static final int REORDER_MOVE_TO_TOP = 1;
81     public static final int REORDER_MOVE_TO_ORIGINAL_POSITION = 2;
82 
83     @IntDef(prefix = { "REORDER_MODE_" }, value = {
84             REORDER_KEEP_IN_PLACE,
85             REORDER_MOVE_TO_TOP,
86             REORDER_MOVE_TO_ORIGINAL_POSITION
87     })
88     public @interface ReorderMode {}
89 
90     private final WindowManagerService mService;
91     private final StatusBarManagerInternal mStatusBar;
92     private IRecentsAnimationRunner mRunner;
93     private final RecentsAnimationCallbacks mCallbacks;
94     private final ArrayList<TaskAnimationAdapter> mPendingAnimations = new ArrayList<>();
95     private final int mDisplayId;
96     private final Runnable mFailsafeRunnable = () ->
97             cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "failSafeRunnable");
98 
99     final Object mLock = new Object();
100 
101     // The recents component app token that is shown behind the visibile tasks
102     private AppWindowToken mTargetAppToken;
103     private int mTargetActivityType;
104     private Rect mMinimizedHomeBounds = new Rect();
105 
106     // We start the RecentsAnimationController in a pending-start state since we need to wait for
107     // the wallpaper/activity to draw before we can give control to the handler to start animating
108     // the visible task surfaces
109     private boolean mPendingStart = true;
110 
111     // Set when the animation has been canceled
112     private boolean mCanceled;
113 
114     // Whether or not the input consumer is enabled. The input consumer must be both registered and
115     // enabled for it to start intercepting touch events.
116     private boolean mInputConsumerEnabled;
117 
118     // Whether or not the recents animation should cause the primary split-screen stack to be
119     // minimized
120     private boolean mSplitScreenMinimized;
121 
122     private final Rect mTmpRect = new Rect();
123 
124     private boolean mLinkedToDeathOfRunner;
125 
126     private boolean mCancelWithDeferredScreenshot;
127 
128     private boolean mCancelOnNextTransitionStart;
129 
130     /**
131      * Animates the screenshot of task that used to be controlled by RecentsAnimation.
132      * @see {@link #cancelOnNextTransitionStart}
133      */
134     SurfaceAnimator mRecentScreenshotAnimator;
135 
136     final AppTransitionListener mAppTransitionListener = new AppTransitionListener() {
137         @Override
138         public int onAppTransitionStartingLocked(int transit, long duration,
139                 long statusBarAnimationStartTime, long statusBarAnimationDuration) {
140             onTransitionStart();
141             mService.mRoot.getDisplayContent(mDisplayId).mAppTransition
142                     .unregisterListener(this);
143             return 0;
144         }
145     };
146 
147     public interface RecentsAnimationCallbacks {
148         /** Callback when recents animation is finished. */
onAnimationFinished(@eorderMode int reorderMode, boolean runSychronously, boolean sendUserLeaveHint)149         void onAnimationFinished(@ReorderMode int reorderMode, boolean runSychronously,
150                 boolean sendUserLeaveHint);
151     }
152 
153     private final IRecentsAnimationController mController =
154             new IRecentsAnimationController.Stub() {
155 
156         @Override
157         public TaskSnapshot screenshotTask(int taskId) {
158             if (DEBUG_RECENTS_ANIMATIONS) Slog.d(TAG, "screenshotTask(" + taskId + "):"
159                     + " mCanceled=" + mCanceled);
160             final long token = Binder.clearCallingIdentity();
161             try {
162                 synchronized (mService.getWindowManagerLock()) {
163                     if (mCanceled) {
164                         return null;
165                     }
166                     for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
167                         final TaskAnimationAdapter adapter = mPendingAnimations.get(i);
168                         final Task task = adapter.mTask;
169                         if (task.mTaskId == taskId) {
170                             final TaskSnapshotController snapshotController =
171                                     mService.mTaskSnapshotController;
172                             final ArraySet<Task> tasks = Sets.newArraySet(task);
173                             snapshotController.snapshotTasks(tasks);
174                             snapshotController.addSkipClosingAppSnapshotTasks(tasks);
175                             return snapshotController.getSnapshot(taskId, 0 /* userId */,
176                                     false /* restoreFromDisk */, false /* reducedResolution */);
177                         }
178                     }
179                     return null;
180                 }
181             } finally {
182                 Binder.restoreCallingIdentity(token);
183             }
184         }
185 
186         @Override
187         public void finish(boolean moveHomeToTop, boolean sendUserLeaveHint) {
188             if (DEBUG_RECENTS_ANIMATIONS) Slog.d(TAG, "finish(" + moveHomeToTop + "):"
189                     + " mCanceled=" + mCanceled);
190             final long token = Binder.clearCallingIdentity();
191             try {
192                 synchronized (mService.getWindowManagerLock()) {
193                     if (mCanceled) {
194                         return;
195                     }
196                 }
197 
198                 // Note, the callback will handle its own synchronization, do not lock on WM lock
199                 // prior to calling the callback
200                 mCallbacks.onAnimationFinished(moveHomeToTop
201                         ? REORDER_MOVE_TO_TOP
202                         : REORDER_MOVE_TO_ORIGINAL_POSITION,
203                         true /* runSynchronously */, sendUserLeaveHint);
204                 final DisplayContent dc = mService.mRoot.getDisplayContent(mDisplayId);
205                 dc.mBoundsAnimationController.setAnimationType(FADE_IN);
206             } finally {
207                 Binder.restoreCallingIdentity(token);
208             }
209         }
210 
211         @Override
212         public void setAnimationTargetsBehindSystemBars(boolean behindSystemBars)
213                 throws RemoteException {
214             final long token = Binder.clearCallingIdentity();
215             try {
216                 synchronized (mService.getWindowManagerLock()) {
217                     for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
218                         final Task task = mPendingAnimations.get(i).mTask;
219                         if (task.getActivityType() != mTargetActivityType) {
220                             task.setCanAffectSystemUiFlags(behindSystemBars);
221                         }
222                     }
223                     mService.mWindowPlacerLocked.requestTraversal();
224                 }
225             } finally {
226                 Binder.restoreCallingIdentity(token);
227             }
228         }
229 
230         @Override
231         public void setInputConsumerEnabled(boolean enabled) {
232             if (DEBUG_RECENTS_ANIMATIONS) Slog.d(TAG, "setInputConsumerEnabled(" + enabled + "):"
233                     + " mCanceled=" + mCanceled);
234             final long token = Binder.clearCallingIdentity();
235             try {
236                 synchronized (mService.getWindowManagerLock()) {
237                     if (mCanceled) {
238                         return;
239                     }
240 
241                     mInputConsumerEnabled = enabled;
242                     final InputMonitor inputMonitor =
243                             mService.mRoot.getDisplayContent(mDisplayId).getInputMonitor();
244                     inputMonitor.updateInputWindowsLw(true /*force*/);
245                     mService.scheduleAnimationLocked();
246                 }
247             } finally {
248                 Binder.restoreCallingIdentity(token);
249             }
250         }
251 
252         @Override
253         public void setSplitScreenMinimized(boolean minimized) {
254             final long token = Binder.clearCallingIdentity();
255             try {
256                 synchronized (mService.getWindowManagerLock()) {
257                     if (mCanceled) {
258                         return;
259                     }
260 
261                     mSplitScreenMinimized = minimized;
262                     mService.checkSplitScreenMinimizedChanged(true /* animate */);
263                 }
264             } finally {
265                 Binder.restoreCallingIdentity(token);
266             }
267         }
268 
269         @Override
270         public void hideCurrentInputMethod() {
271             final long token = Binder.clearCallingIdentity();
272             try {
273                 final InputMethodManagerInternal inputMethodManagerInternal =
274                         LocalServices.getService(InputMethodManagerInternal.class);
275                 if (inputMethodManagerInternal != null) {
276                     inputMethodManagerInternal.hideCurrentInputMethod();
277                 }
278             } finally {
279                 Binder.restoreCallingIdentity(token);
280             }
281         }
282 
283         @Override
284         public void setCancelWithDeferredScreenshot(boolean screenshot) {
285             synchronized (mLock) {
286                 setCancelWithDeferredScreenshotLocked(screenshot);
287             }
288         }
289 
290         @Override
291         public void cleanupScreenshot() {
292             synchronized (mLock) {
293                 if (mRecentScreenshotAnimator != null) {
294                     mRecentScreenshotAnimator.cancelAnimation();
295                     mRecentScreenshotAnimator = null;
296                 }
297             }
298         }
299     };
300 
301     /**
302      * @param remoteAnimationRunner The remote runner which should be notified when the animation is
303      *                              ready to start or has been canceled
304      * @param callbacks Callbacks to be made when the animation finishes
305      */
RecentsAnimationController(WindowManagerService service, IRecentsAnimationRunner remoteAnimationRunner, RecentsAnimationCallbacks callbacks, int displayId)306     RecentsAnimationController(WindowManagerService service,
307             IRecentsAnimationRunner remoteAnimationRunner, RecentsAnimationCallbacks callbacks,
308             int displayId) {
309         mService = service;
310         mRunner = remoteAnimationRunner;
311         mCallbacks = callbacks;
312         mDisplayId = displayId;
313         mStatusBar = LocalServices.getService(StatusBarManagerInternal.class);
314     }
315 
initialize(int targetActivityType, SparseBooleanArray recentTaskIds)316     public void initialize(int targetActivityType, SparseBooleanArray recentTaskIds) {
317         initialize(mService.mRoot.getDisplayContent(mDisplayId), targetActivityType, recentTaskIds);
318     }
319 
320     /**
321      * Initializes the recents animation controller. This is a separate call from the constructor
322      * because it may call cancelAnimation() which needs to properly clean up the controller
323      * in the window manager.
324      */
325     @VisibleForTesting
initialize(DisplayContent dc, int targetActivityType, SparseBooleanArray recentTaskIds)326     void initialize(DisplayContent dc, int targetActivityType, SparseBooleanArray recentTaskIds) {
327         mTargetActivityType = targetActivityType;
328         dc.mAppTransition.registerListenerLocked(mAppTransitionListener);
329 
330         // Make leashes for each of the visible/target tasks and add it to the recents animation to
331         // be started
332         final ArrayList<Task> visibleTasks = dc.getVisibleTasks();
333         final TaskStack targetStack = dc.getStack(WINDOWING_MODE_UNDEFINED, targetActivityType);
334         if (targetStack != null) {
335             for (int i = targetStack.getChildCount() - 1; i >= 0; i--) {
336                 final Task t = targetStack.getChildAt(i);
337                 if (!visibleTasks.contains(t)) {
338                     visibleTasks.add(t);
339                 }
340             }
341         }
342         final int taskCount = visibleTasks.size();
343         for (int i = 0; i < taskCount; i++) {
344             final Task task = visibleTasks.get(i);
345             final WindowConfiguration config = task.getWindowConfiguration();
346             if (config.tasksAreFloating()
347                     || config.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
348                 continue;
349             }
350             addAnimation(task, !recentTaskIds.get(task.mTaskId));
351         }
352 
353         // Skip the animation if there is nothing to animate
354         if (mPendingAnimations.isEmpty()) {
355             cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "initialize-noVisibleTasks");
356             return;
357         }
358 
359         try {
360             linkToDeathOfRunner();
361         } catch (RemoteException e) {
362             cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "initialize-failedToLinkToDeath");
363             return;
364         }
365 
366         // Adjust the wallpaper visibility for the showing target activity
367         final AppWindowToken recentsComponentAppToken = dc.getStack(WINDOWING_MODE_UNDEFINED,
368                 targetActivityType).getTopChild().getTopFullscreenAppToken();
369         if (recentsComponentAppToken != null) {
370             if (DEBUG_RECENTS_ANIMATIONS) Slog.d(TAG, "setHomeApp("
371                     + recentsComponentAppToken.getName() + ")");
372             mTargetAppToken = recentsComponentAppToken;
373             if (recentsComponentAppToken.windowsCanBeWallpaperTarget()) {
374                 dc.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
375                 dc.setLayoutNeeded();
376             }
377         }
378 
379         // Save the minimized home height
380         final TaskStack dockedStack = dc.getSplitScreenPrimaryStackIgnoringVisibility();
381         dc.getDockedDividerController().getHomeStackBoundsInDockedMode(
382                 dc.getConfiguration(),
383                 dockedStack == null ? DOCKED_INVALID : dockedStack.getDockSide(),
384                 mMinimizedHomeBounds);
385 
386         mService.mWindowPlacerLocked.performSurfacePlacement();
387 
388         // Notify that the animation has started
389         mStatusBar.onRecentsAnimationStateChanged(true /* running */);
390     }
391 
392     @VisibleForTesting
addAnimation(Task task, boolean isRecentTaskInvisible)393     AnimationAdapter addAnimation(Task task, boolean isRecentTaskInvisible) {
394         if (DEBUG_RECENTS_ANIMATIONS) Slog.d(TAG, "addAnimation(" + task.getName() + ")");
395         final TaskAnimationAdapter taskAdapter = new TaskAnimationAdapter(task,
396                 isRecentTaskInvisible);
397         task.startAnimation(task.getPendingTransaction(), taskAdapter, false /* hidden */);
398         task.commitPendingTransaction();
399         mPendingAnimations.add(taskAdapter);
400         return taskAdapter;
401     }
402 
403     @VisibleForTesting
removeAnimation(TaskAnimationAdapter taskAdapter)404     void removeAnimation(TaskAnimationAdapter taskAdapter) {
405         if (DEBUG_RECENTS_ANIMATIONS) Slog.d(TAG, "removeAnimation("
406                 + taskAdapter.mTask.mTaskId + ")");
407         taskAdapter.mTask.setCanAffectSystemUiFlags(true);
408         taskAdapter.mCapturedFinishCallback.onAnimationFinished(taskAdapter);
409         mPendingAnimations.remove(taskAdapter);
410     }
411 
startAnimation()412     void startAnimation() {
413         if (DEBUG_RECENTS_ANIMATIONS) Slog.d(TAG, "startAnimation(): mPendingStart=" + mPendingStart
414                 + " mCanceled=" + mCanceled);
415         if (!mPendingStart || mCanceled) {
416             // Skip starting if we've already started or canceled the animation
417             return;
418         }
419         try {
420             final ArrayList<RemoteAnimationTarget> appAnimations = new ArrayList<>();
421             for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
422                 final TaskAnimationAdapter taskAdapter = mPendingAnimations.get(i);
423                 final RemoteAnimationTarget target = taskAdapter.createRemoteAnimationApp();
424                 if (target != null) {
425                     appAnimations.add(target);
426                 } else {
427                     removeAnimation(taskAdapter);
428                 }
429             }
430 
431             // Skip the animation if there is nothing to animate
432             if (appAnimations.isEmpty()) {
433                 cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "startAnimation-noAppWindows");
434                 return;
435             }
436 
437             final RemoteAnimationTarget[] appTargets = appAnimations.toArray(
438                     new RemoteAnimationTarget[appAnimations.size()]);
439             mPendingStart = false;
440 
441             // Perform layout if it was scheduled before to make sure that we get correct content
442             // insets for the target app window after a rotation
443             final DisplayContent displayContent = mService.mRoot.getDisplayContent(mDisplayId);
444             displayContent.performLayout(false /* initial */, false /* updateInputWindows */);
445 
446             final Rect minimizedHomeBounds = mTargetAppToken != null
447                     && mTargetAppToken.inSplitScreenSecondaryWindowingMode()
448                             ? mMinimizedHomeBounds
449                             : null;
450             final Rect contentInsets;
451             if (mTargetAppToken != null && mTargetAppToken.findMainWindow() != null) {
452                 contentInsets = mTargetAppToken.findMainWindow().getContentInsets();
453             } else {
454                 // If the window for the activity had not yet been created, use the display insets.
455                 mService.getStableInsets(mDisplayId, mTmpRect);
456                 contentInsets = mTmpRect;
457             }
458             mRunner.onAnimationStart(mController, appTargets, contentInsets, minimizedHomeBounds);
459             if (DEBUG_RECENTS_ANIMATIONS) {
460                 Slog.d(TAG, "startAnimation(): Notify animation start:");
461                 for (int i = 0; i < mPendingAnimations.size(); i++) {
462                     final Task task = mPendingAnimations.get(i).mTask;
463                     Slog.d(TAG, "\t" + task.mTaskId);
464                 }
465             }
466         } catch (RemoteException e) {
467             Slog.e(TAG, "Failed to start recents animation", e);
468         }
469         final SparseIntArray reasons = new SparseIntArray();
470         reasons.put(WINDOWING_MODE_FULLSCREEN, APP_TRANSITION_RECENTS_ANIM);
471         mService.mAtmInternal.notifyAppTransitionStarting(reasons, SystemClock.uptimeMillis());
472     }
473 
cancelAnimation(@eorderMode int reorderMode, String reason)474     void cancelAnimation(@ReorderMode int reorderMode, String reason) {
475         cancelAnimation(reorderMode, false /* runSynchronously */, false /*screenshot */, reason);
476     }
477 
cancelAnimationSynchronously(@eorderMode int reorderMode, String reason)478     void cancelAnimationSynchronously(@ReorderMode int reorderMode, String reason) {
479         cancelAnimation(reorderMode, true /* runSynchronously */, false /* screenshot */, reason);
480     }
481 
cancelAnimationWithScreenShot()482     void cancelAnimationWithScreenShot() {
483         cancelAnimation(REORDER_KEEP_IN_PLACE, true /* sync */, true /* screenshot */,
484                 "stackOrderChanged");
485     }
486 
cancelAnimation(@eorderMode int reorderMode, boolean runSynchronously, boolean screenshot, String reason)487     private void cancelAnimation(@ReorderMode int reorderMode, boolean runSynchronously,
488             boolean screenshot, String reason) {
489         if (DEBUG_RECENTS_ANIMATIONS) Slog.d(TAG, "cancelAnimation(): reason=" + reason
490                 + " runSynchronously=" + runSynchronously);
491         synchronized (mService.getWindowManagerLock()) {
492             if (mCanceled) {
493                 // We've already canceled the animation
494                 return;
495             }
496             mService.mH.removeCallbacks(mFailsafeRunnable);
497             mCanceled = true;
498             try {
499                 if (screenshot) {
500                     // Screen shot previous task when next task starts transition.
501                     final Task task = mPendingAnimations.get(0).mTask;
502                     screenshotRecentTask(task, reorderMode, runSynchronously);
503                     mRunner.onAnimationCanceled(true /* deferredWithScreenshot */);
504                     return;
505                 }
506                 mRunner.onAnimationCanceled(false /* deferredWithScreenshot */);
507             } catch (RemoteException e) {
508                 Slog.e(TAG, "Failed to cancel recents animation", e);
509             }
510             // Clean up and return to the previous app
511             mCallbacks.onAnimationFinished(reorderMode, runSynchronously,
512                     false /* sendUserLeaveHint */);
513         }
514     }
515 
516     /**
517      * Cancel recents animation when the next app transition starts.
518      * <p>
519      * When we cancel the recents animation due to a stack order change, we can't just cancel it
520      * immediately as it would lead to a flicker in Launcher if we just remove the task from the
521      * leash. Instead we screenshot the previous task and replace the child of the leash with the
522      * screenshot, so that Launcher can still control the leash lifecycle & make the next app
523      * transition animate smoothly without flickering.
524      */
cancelOnNextTransitionStart()525     void cancelOnNextTransitionStart() {
526         mCancelOnNextTransitionStart = true;
527     }
528 
setCancelWithDeferredScreenshotLocked(boolean screenshot)529     void setCancelWithDeferredScreenshotLocked(boolean screenshot) {
530         mCancelWithDeferredScreenshot = screenshot;
531     }
532 
shouldCancelWithDeferredScreenshot()533     boolean shouldCancelWithDeferredScreenshot() {
534         return mCancelWithDeferredScreenshot;
535     }
536 
onTransitionStart()537     void onTransitionStart() {
538         if (mCanceled) {
539             return;
540         }
541 
542         if (mCancelOnNextTransitionStart) {
543             mCancelOnNextTransitionStart = false;
544             cancelAnimationWithScreenShot();
545         }
546     }
547 
screenshotRecentTask(Task task, @ReorderMode int reorderMode, boolean runSynchronously)548     void screenshotRecentTask(Task task, @ReorderMode int reorderMode, boolean runSynchronously) {
549         final TaskScreenshotAnimatable animatable = TaskScreenshotAnimatable.create(task);
550         if (animatable != null) {
551             mRecentScreenshotAnimator = new SurfaceAnimator(
552                     animatable,
553                     () -> {
554                         if (DEBUG_RECENTS_ANIMATIONS) {
555                             Slog.d(TAG, "mRecentScreenshotAnimator finish");
556                         }
557                         mCallbacks.onAnimationFinished(reorderMode, runSynchronously,
558                                 false /* sendUserLeaveHint */);
559                     }, mService);
560             mRecentScreenshotAnimator.transferAnimation(task.mSurfaceAnimator);
561         }
562     }
563 
cleanupAnimation(@eorderMode int reorderMode)564     void cleanupAnimation(@ReorderMode int reorderMode) {
565         if (DEBUG_RECENTS_ANIMATIONS) Slog.d(TAG,
566                 "cleanupAnimation(): Notify animation finished mPendingAnimations="
567                         + mPendingAnimations.size() + " reorderMode=" + reorderMode);
568         for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
569             final TaskAnimationAdapter taskAdapter = mPendingAnimations.get(i);
570             if (reorderMode == REORDER_MOVE_TO_TOP || reorderMode == REORDER_KEEP_IN_PLACE) {
571                 taskAdapter.mTask.dontAnimateDimExit();
572             }
573             removeAnimation(taskAdapter);
574         }
575 
576         // Clear any pending failsafe runnables
577         mService.mH.removeCallbacks(mFailsafeRunnable);
578 
579         // Clear references to the runner
580         unlinkToDeathOfRunner();
581         mRunner = null;
582         mCanceled = true;
583 
584         // Make sure previous animator has cleaned-up.
585         if (mRecentScreenshotAnimator != null) {
586             mRecentScreenshotAnimator.cancelAnimation();
587             mRecentScreenshotAnimator = null;
588         }
589 
590         // Update the input windows after the animation is complete
591         final InputMonitor inputMonitor =
592                 mService.mRoot.getDisplayContent(mDisplayId).getInputMonitor();
593         inputMonitor.updateInputWindowsLw(true /*force*/);
594 
595         // We have deferred all notifications to the target app as a part of the recents animation,
596         // so if we are actually transitioning there, notify again here
597         if (mTargetAppToken != null) {
598             if (reorderMode == REORDER_MOVE_TO_TOP || reorderMode == REORDER_KEEP_IN_PLACE) {
599                 mService.mRoot.getDisplayContent(mDisplayId)
600                         .mAppTransition.notifyAppTransitionFinishedLocked(mTargetAppToken.token);
601             }
602         }
603 
604         // Notify that the animation has ended
605         mStatusBar.onRecentsAnimationStateChanged(false /* running */);
606     }
607 
scheduleFailsafe()608     void scheduleFailsafe() {
609         mService.mH.postDelayed(mFailsafeRunnable, FAILSAFE_DELAY);
610     }
611 
linkToDeathOfRunner()612     private void linkToDeathOfRunner() throws RemoteException {
613         if (!mLinkedToDeathOfRunner) {
614             mRunner.asBinder().linkToDeath(this, 0);
615             mLinkedToDeathOfRunner = true;
616         }
617     }
618 
unlinkToDeathOfRunner()619     private void unlinkToDeathOfRunner() {
620         if (mLinkedToDeathOfRunner) {
621             mRunner.asBinder().unlinkToDeath(this, 0);
622             mLinkedToDeathOfRunner = false;
623         }
624     }
625 
626     @Override
binderDied()627     public void binderDied() {
628         cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "binderDied");
629 
630         synchronized (mService.getWindowManagerLock()) {
631             // Clear associated input consumers on runner death
632             final InputMonitor inputMonitor =
633                     mService.mRoot.getDisplayContent(mDisplayId).getInputMonitor();
634             inputMonitor.destroyInputConsumer(INPUT_CONSUMER_RECENTS_ANIMATION);
635         }
636     }
637 
checkAnimationReady(WallpaperController wallpaperController)638     void checkAnimationReady(WallpaperController wallpaperController) {
639         if (mPendingStart) {
640             final boolean wallpaperReady = !isTargetOverWallpaper()
641                     || (wallpaperController.getWallpaperTarget() != null
642                             && wallpaperController.wallpaperTransitionReady());
643             if (wallpaperReady) {
644                 mService.getRecentsAnimationController().startAnimation();
645             }
646         }
647     }
648 
isSplitScreenMinimized()649     boolean isSplitScreenMinimized() {
650         return mSplitScreenMinimized;
651     }
652 
isWallpaperVisible(WindowState w)653     boolean isWallpaperVisible(WindowState w) {
654         return w != null && w.mAppToken != null && mTargetAppToken == w.mAppToken
655                 && isTargetOverWallpaper();
656     }
657 
658     /**
659      * @return Whether to use the input consumer to override app input to route home/recents.
660      */
shouldApplyInputConsumer(AppWindowToken appToken)661     boolean shouldApplyInputConsumer(AppWindowToken appToken) {
662         // Only apply the input consumer if it is enabled, it is not the target (home/recents)
663         // being revealed with the transition, and we are actively animating the app as a part of
664         // the animation
665         return mInputConsumerEnabled && mTargetAppToken != appToken && isAnimatingApp(appToken);
666     }
667 
updateInputConsumerForApp(InputWindowHandle inputWindowHandle, boolean hasFocus)668     boolean updateInputConsumerForApp(InputWindowHandle inputWindowHandle,
669             boolean hasFocus) {
670         // Update the input consumer touchable region to match the target app main window
671         final WindowState targetAppMainWindow = mTargetAppToken != null
672                 ? mTargetAppToken.findMainWindow()
673                 : null;
674         if (targetAppMainWindow != null) {
675             targetAppMainWindow.getBounds(mTmpRect);
676             inputWindowHandle.hasFocus = hasFocus;
677             inputWindowHandle.touchableRegion.set(mTmpRect);
678             return true;
679         }
680         return false;
681     }
682 
isTargetApp(AppWindowToken token)683     boolean isTargetApp(AppWindowToken token) {
684         return mTargetAppToken != null && token == mTargetAppToken;
685     }
686 
isTargetOverWallpaper()687     private boolean isTargetOverWallpaper() {
688         if (mTargetAppToken == null) {
689             return false;
690         }
691         return mTargetAppToken.windowsCanBeWallpaperTarget();
692     }
693 
isAnimatingTask(Task task)694     boolean isAnimatingTask(Task task) {
695         for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
696             if (task == mPendingAnimations.get(i).mTask) {
697                 return true;
698             }
699         }
700         return false;
701     }
702 
isAnimatingApp(AppWindowToken appToken)703     private boolean isAnimatingApp(AppWindowToken appToken) {
704         for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
705             final Task task = mPendingAnimations.get(i).mTask;
706             for (int j = task.getChildCount() - 1; j >= 0; j--) {
707                 final AppWindowToken app = task.getChildAt(j);
708                 if (app == appToken) {
709                     return true;
710                 }
711             }
712         }
713         return false;
714     }
715 
716     @VisibleForTesting
717     class TaskAnimationAdapter implements AnimationAdapter {
718 
719         private final Task mTask;
720         private SurfaceControl mCapturedLeash;
721         private OnAnimationFinishedCallback mCapturedFinishCallback;
722         private final boolean mIsRecentTaskInvisible;
723         private RemoteAnimationTarget mTarget;
724         private final Point mPosition = new Point();
725         private final Rect mBounds = new Rect();
726 
TaskAnimationAdapter(Task task, boolean isRecentTaskInvisible)727         TaskAnimationAdapter(Task task, boolean isRecentTaskInvisible) {
728             mTask = task;
729             mIsRecentTaskInvisible = isRecentTaskInvisible;
730             final WindowContainer container = mTask.getParent();
731             container.getRelativeDisplayedPosition(mPosition);
732             mBounds.set(container.getDisplayedBounds());
733         }
734 
createRemoteAnimationApp()735         RemoteAnimationTarget createRemoteAnimationApp() {
736             final AppWindowToken topApp = mTask.getTopVisibleAppToken();
737             final WindowState mainWindow = topApp != null
738                     ? topApp.findMainWindow()
739                     : null;
740             if (mainWindow == null) {
741                 return null;
742             }
743             final Rect insets = new Rect();
744             mainWindow.getContentInsets(insets);
745             InsetUtils.addInsets(insets, mainWindow.mAppToken.getLetterboxInsets());
746             final int mode = topApp.getActivityType() == mTargetActivityType
747                     ? MODE_OPENING
748                     : MODE_CLOSING;
749             mTarget = new RemoteAnimationTarget(mTask.mTaskId, mode, mCapturedLeash,
750                     !topApp.fillsParent(), mainWindow.mWinAnimator.mLastClipRect,
751                     insets, mTask.getPrefixOrderIndex(), mPosition, mBounds,
752                     mTask.getWindowConfiguration(), mIsRecentTaskInvisible, null, null);
753             return mTarget;
754         }
755 
756         @Override
getShowWallpaper()757         public boolean getShowWallpaper() {
758             return false;
759         }
760 
761         @Override
getBackgroundColor()762         public int getBackgroundColor() {
763             return 0;
764         }
765 
766         @Override
startAnimation(SurfaceControl animationLeash, Transaction t, OnAnimationFinishedCallback finishCallback)767         public void startAnimation(SurfaceControl animationLeash, Transaction t,
768                 OnAnimationFinishedCallback finishCallback) {
769             // Restore z-layering, position and stack crop until client has a chance to modify it.
770             t.setLayer(animationLeash, mTask.getPrefixOrderIndex());
771             t.setPosition(animationLeash, mPosition.x, mPosition.y);
772             mTmpRect.set(mBounds);
773             mTmpRect.offsetTo(0, 0);
774             t.setWindowCrop(animationLeash, mTmpRect);
775             mCapturedLeash = animationLeash;
776             mCapturedFinishCallback = finishCallback;
777         }
778 
779         @Override
onAnimationCancelled(SurfaceControl animationLeash)780         public void onAnimationCancelled(SurfaceControl animationLeash) {
781             cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "taskAnimationAdapterCanceled");
782         }
783 
784         @Override
getDurationHint()785         public long getDurationHint() {
786             return 0;
787         }
788 
789         @Override
getStatusBarTransitionsStartTime()790         public long getStatusBarTransitionsStartTime() {
791             return SystemClock.uptimeMillis();
792         }
793 
794         @Override
dump(PrintWriter pw, String prefix)795         public void dump(PrintWriter pw, String prefix) {
796             pw.print(prefix); pw.println("task=" + mTask);
797             if (mTarget != null) {
798                 pw.print(prefix); pw.println("Target:");
799                 mTarget.dump(pw, prefix + "  ");
800             } else {
801                 pw.print(prefix); pw.println("Target: null");
802             }
803             pw.println("mIsRecentTaskInvisible=" + mIsRecentTaskInvisible);
804             pw.println("mPosition=" + mPosition);
805             pw.println("mBounds=" + mBounds);
806             pw.println("mIsRecentTaskInvisible=" + mIsRecentTaskInvisible);
807         }
808 
809         @Override
writeToProto(ProtoOutputStream proto)810         public void writeToProto(ProtoOutputStream proto) {
811             final long token = proto.start(REMOTE);
812             if (mTarget != null) {
813                 mTarget.writeToProto(proto, TARGET);
814             }
815             proto.end(token);
816         }
817     }
818 
dump(PrintWriter pw, String prefix)819     public void dump(PrintWriter pw, String prefix) {
820         final String innerPrefix = prefix + "  ";
821         pw.print(prefix); pw.println(RecentsAnimationController.class.getSimpleName() + ":");
822         pw.print(innerPrefix); pw.println("mPendingStart=" + mPendingStart);
823         pw.print(innerPrefix); pw.println("mPendingAnimations=" + mPendingAnimations.size());
824         pw.print(innerPrefix); pw.println("mCanceled=" + mCanceled);
825         pw.print(innerPrefix); pw.println("mInputConsumerEnabled=" + mInputConsumerEnabled);
826         pw.print(innerPrefix); pw.println("mSplitScreenMinimized=" + mSplitScreenMinimized);
827         pw.print(innerPrefix); pw.println("mTargetAppToken=" + mTargetAppToken);
828         pw.print(innerPrefix); pw.println("isTargetOverWallpaper=" + isTargetOverWallpaper());
829     }
830 }
831