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