1 /*
2  * Copyright (C) 2019 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 package com.android.quickstep;
17 
18 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
19 
20 import static com.android.launcher3.Flags.enableHandleDelayedGestureCallbacks;
21 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
22 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
23 import static com.android.launcher3.util.NavigationMode.NO_BUTTON;
24 import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
25 import static com.android.quickstep.GestureState.STATE_END_TARGET_ANIMATION_FINISHED;
26 import static com.android.quickstep.GestureState.STATE_RECENTS_ANIMATION_INITIALIZED;
27 import static com.android.quickstep.GestureState.STATE_RECENTS_ANIMATION_STARTED;
28 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.START_RECENTS_ANIMATION;
29 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
30 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
31 
32 import android.app.ActivityManager;
33 import android.app.ActivityOptions;
34 import android.content.Context;
35 import android.content.Intent;
36 import android.os.SystemProperties;
37 import android.util.Log;
38 import android.view.RemoteAnimationTarget;
39 
40 import androidx.annotation.NonNull;
41 import androidx.annotation.Nullable;
42 import androidx.annotation.UiThread;
43 
44 import com.android.internal.util.ArrayUtils;
45 import com.android.launcher3.Utilities;
46 import com.android.launcher3.config.FeatureFlags;
47 import com.android.launcher3.util.DisplayController;
48 import com.android.quickstep.util.ActiveGestureLog;
49 import com.android.quickstep.util.SystemUiFlagUtils;
50 import com.android.quickstep.views.RecentsView;
51 import com.android.systemui.shared.recents.model.ThumbnailData;
52 import com.android.systemui.shared.system.ActivityManagerWrapper;
53 import com.android.systemui.shared.system.QuickStepContract;
54 import com.android.systemui.shared.system.TaskStackChangeListener;
55 import com.android.systemui.shared.system.TaskStackChangeListeners;
56 
57 import java.io.PrintWriter;
58 import java.util.HashMap;
59 
60 public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAnimationListener {
61     public static final boolean ENABLE_SHELL_TRANSITIONS = true;
62     public static final boolean SHELL_TRANSITIONS_ROTATION = ENABLE_SHELL_TRANSITIONS
63             && SystemProperties.getBoolean("persist.wm.debug.shell_transit_rotate", false);
64 
65     private final Context mCtx;
66     private RecentsAnimationController mController;
67     private RecentsAnimationCallbacks mCallbacks;
68     private RecentsAnimationTargets mTargets;
69     // Temporary until we can hook into gesture state events
70     private GestureState mLastGestureState;
71     private RemoteAnimationTarget[] mLastAppearedTaskTargets;
72     private Runnable mLiveTileCleanUpHandler;
73 
74     private boolean mRecentsAnimationStartPending = false;
75     private boolean mShouldIgnoreMotionEvents = false;
76 
77     private final TaskStackChangeListener mLiveTileRestartListener = new TaskStackChangeListener() {
78         @Override
79         public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
80                 boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
81             if (mLastGestureState == null) {
82                 TaskStackChangeListeners.getInstance().unregisterTaskStackListener(
83                         mLiveTileRestartListener);
84                 return;
85             }
86             BaseContainerInterface containerInterface = mLastGestureState.getContainerInterface();
87             if (containerInterface.isInLiveTileMode()
88                     && containerInterface.getCreatedContainer() != null) {
89                 RecentsView recentsView = containerInterface.getCreatedContainer()
90                         .getOverviewPanel();
91                 if (recentsView != null) {
92                     recentsView.launchSideTaskInLiveTileModeForRestartedApp(task.taskId);
93                     TaskStackChangeListeners.getInstance().unregisterTaskStackListener(
94                             mLiveTileRestartListener);
95                 }
96             }
97         }
98     };
99 
TaskAnimationManager(Context ctx)100     TaskAnimationManager(Context ctx) {
101         mCtx = ctx;
102     }
103 
getSystemUiProxy()104     SystemUiProxy getSystemUiProxy() {
105         return SystemUiProxy.INSTANCE.get(mCtx);
106     }
107 
108     /**
109      * Preloads the recents animation.
110      */
preloadRecentsAnimation(Intent intent)111     public void preloadRecentsAnimation(Intent intent) {
112         // Pass null animation handler to indicate this start is for preloading
113         UI_HELPER_EXECUTOR.execute(() -> ActivityManagerWrapper.getInstance()
114                 .startRecentsActivity(intent, 0, null, null, null));
115     }
116 
shouldIgnoreMotionEvents()117     boolean shouldIgnoreMotionEvents() {
118         return mShouldIgnoreMotionEvents;
119     }
120 
notifyNewGestureStart()121     void notifyNewGestureStart() {
122         // If mRecentsAnimationStartPending is true at the beginning of a gesture, block all motion
123         // events for this new gesture so that this new gesture does not interfere with the
124         // previously-requested recents animation. Otherwise, clean up mShouldIgnoreMotionEvents.
125         // NOTE: this can lead to misleading logs
126         mShouldIgnoreMotionEvents = mRecentsAnimationStartPending;
127     }
128 
129     /**
130      * Starts a new recents animation for the activity with the given {@param intent}.
131      */
132     @UiThread
startRecentsAnimation(@onNull GestureState gestureState, Intent intent, RecentsAnimationCallbacks.RecentsAnimationListener listener)133     public RecentsAnimationCallbacks startRecentsAnimation(@NonNull GestureState gestureState,
134             Intent intent, RecentsAnimationCallbacks.RecentsAnimationListener listener) {
135         ActiveGestureLog.INSTANCE.addLog(
136                 /* event= */ "startRecentsAnimation",
137                 /* gestureEvent= */ START_RECENTS_ANIMATION);
138         // Notify if recents animation is still running
139         if (mController != null) {
140             String msg = "New recents animation started before old animation completed";
141             if (FeatureFlags.IS_STUDIO_BUILD) {
142                 throw new IllegalArgumentException(msg);
143             } else {
144                 Log.e("TaskAnimationManager", msg, new Exception());
145             }
146         }
147         // But force-finish it anyways
148         finishRunningRecentsAnimation(false /* toHome */, true /* forceFinish */,
149                 null /* forceFinishCb */);
150 
151         if (mCallbacks != null) {
152             // If mCallbacks still != null, that means we are getting this startRecentsAnimation()
153             // before the previous one got onRecentsAnimationStart(). In that case, cleanup the
154             // previous animation so it doesn't mess up/listen to state changes in this animation.
155             cleanUpRecentsAnimation(mCallbacks);
156         }
157 
158         final BaseContainerInterface containerInterface = gestureState.getContainerInterface();
159         mLastGestureState = gestureState;
160         RecentsAnimationCallbacks newCallbacks = new RecentsAnimationCallbacks(
161                 getSystemUiProxy(), containerInterface.allowMinimizeSplitScreen());
162         mCallbacks = newCallbacks;
163         mCallbacks.addListener(new RecentsAnimationCallbacks.RecentsAnimationListener() {
164             @Override
165             public void onRecentsAnimationStart(RecentsAnimationController controller,
166                     RecentsAnimationTargets targets) {
167                 if (enableHandleDelayedGestureCallbacks() && mRecentsAnimationStartPending) {
168                     ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
169                             "TaskAnimationManager.startRecentsAnimation(onRecentsAnimationStart): ")
170                             .append("Setting mRecentsAnimationStartPending = false"));
171                     mRecentsAnimationStartPending = false;
172                 }
173                 if (mCallbacks == null) {
174                     // It's possible for the recents animation to have finished and be cleaned up
175                     // by the time we process the start callback, and in that case, just we can skip
176                     // handling this call entirely
177                     return;
178                 }
179                 mController = controller;
180                 mTargets = targets;
181                 // TODO(b/236226779): We can probably get away w/ setting mLastAppearedTaskTargets
182                 //  to all appeared targets directly vs just looking at running ones
183                 int[] runningTaskIds = mLastGestureState.getRunningTaskIds(targets.apps.length > 1);
184                 mLastAppearedTaskTargets = new RemoteAnimationTarget[runningTaskIds.length];
185                 for (int i = 0; i < runningTaskIds.length; i++) {
186                     RemoteAnimationTarget task = mTargets.findTask(runningTaskIds[i]);
187                     mLastAppearedTaskTargets[i] = task;
188                 }
189                 mLastGestureState.updateLastAppearedTaskTargets(mLastAppearedTaskTargets);
190 
191                 if (ENABLE_SHELL_TRANSITIONS && mTargets.hasRecents
192                         // The filtered (MODE_CLOSING) targets only contain 1 home activity.
193                         && mTargets.apps.length == 1
194                         && mTargets.apps[0].windowConfiguration.getActivityType()
195                         == ACTIVITY_TYPE_HOME) {
196                     // This is launching RecentsActivity on top of a 3p launcher. There are no
197                     // other apps need to keep visible so finish the animating state after the
198                     // enter animation of overview is done. Then 3p launcher can be stopped.
199                     mLastGestureState.runOnceAtState(STATE_END_TARGET_ANIMATION_FINISHED, () -> {
200                         if (mLastGestureState != gestureState) return;
201                         // Only finish if the end target is RECENTS. Otherwise, if the target is
202                         // NEW_TASK, startActivityFromRecents will be skipped.
203                         if (mLastGestureState.getEndTarget() == RECENTS) {
204                             finishRunningRecentsAnimation(false /* toHome */,
205                                     true /* forceFinish */, null /* forceFinishCb */);
206                         }
207                     });
208                 }
209             }
210 
211             @Override
212             public void onRecentsAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas) {
213                 if (enableHandleDelayedGestureCallbacks() && mRecentsAnimationStartPending) {
214                     ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
215                             "TaskAnimationManager.startRecentsAnimation")
216                             .append("(onRecentsAnimationCanceled): ")
217                             .append("Setting mRecentsAnimationStartPending = false"));
218                     mRecentsAnimationStartPending = false;
219                 }
220                 cleanUpRecentsAnimation(newCallbacks);
221             }
222 
223             @Override
224             public void onRecentsAnimationFinished(RecentsAnimationController controller) {
225                 if (enableHandleDelayedGestureCallbacks() && mRecentsAnimationStartPending) {
226                     ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
227                             "TaskAnimationManager.startRecentsAnimation")
228                             .append("(onRecentsAnimationFinished): ")
229                             .append("Setting mRecentsAnimationStartPending = false"));
230                     mRecentsAnimationStartPending = false;
231                 }
232                 cleanUpRecentsAnimation(newCallbacks);
233             }
234 
235             private boolean isNonRecentsStartedTasksAppeared(
236                     RemoteAnimationTarget[] appearedTaskTargets) {
237                 // For example, right after swiping from task X to task Y (e.g. from
238                 // AbsSwipeUpHandler#startNewTask), and then task Y starts X immediately
239                 // (e.g. in Y's onResume). The case will be: lastStartedTask=Y and appearedTask=X.
240                 return mLastGestureState.getEndTarget() == GestureState.GestureEndTarget.NEW_TASK
241                         && ArrayUtils.find(appearedTaskTargets,
242                                 mLastGestureState.mLastStartedTaskIdPredicate) == null;
243             }
244 
245             @Override
246             public void onTasksAppeared(RemoteAnimationTarget[] appearedTaskTargets) {
247                 RemoteAnimationTarget appearedTaskTarget = appearedTaskTargets[0];
248                 BaseContainerInterface containerInterface =
249                         mLastGestureState.getContainerInterface();
250 
251                 for (RemoteAnimationTarget compat : appearedTaskTargets) {
252                     if (compat.windowConfiguration.getActivityType() == ACTIVITY_TYPE_HOME
253                             && containerInterface.getCreatedContainer() instanceof RecentsActivity
254                             && DisplayController.getNavigationMode(mCtx) != NO_BUTTON) {
255                         // The only time we get onTasksAppeared() in button navigation with a
256                         // 3p launcher is if the user goes to overview first, and in this case we
257                         // can immediately finish the transition
258                         RecentsView recentsView =
259                                 containerInterface.getCreatedContainer().getOverviewPanel();
260                         if (recentsView != null) {
261                             recentsView.finishRecentsAnimation(true, null);
262                         }
263                         return;
264                     }
265                 }
266 
267                 RemoteAnimationTarget[] nonAppTargets = ENABLE_SHELL_TRANSITIONS
268                         ? null : getSystemUiProxy().onStartingSplitLegacy(
269                                 appearedTaskTargets);
270                 if (nonAppTargets == null) {
271                     nonAppTargets = new RemoteAnimationTarget[0];
272                 }
273                 if ((containerInterface.isInLiveTileMode()
274                             || mLastGestureState.getEndTarget() == RECENTS
275                             || isNonRecentsStartedTasksAppeared(appearedTaskTargets))
276                         && containerInterface.getCreatedContainer() != null) {
277                     RecentsView recentsView =
278                             containerInterface.getCreatedContainer().getOverviewPanel();
279                     if (recentsView != null) {
280                         ActiveGestureLog.INSTANCE.addLog(
281                                 new ActiveGestureLog.CompoundString("Launching side task id=")
282                                         .append(appearedTaskTarget.taskId));
283                         recentsView.launchSideTaskInLiveTileMode(appearedTaskTarget.taskId,
284                                 appearedTaskTargets,
285                                 new RemoteAnimationTarget[0] /* wallpaper */,
286                                 nonAppTargets /* nonApps */);
287                         return;
288                     } else {
289                         ActiveGestureLog.INSTANCE.addLog("Unable to launch side task (no recents)");
290                     }
291                 } else if (nonAppTargets.length > 0) {
292                     TaskViewUtils.createSplitAuxiliarySurfacesAnimator(nonAppTargets /* nonApps */,
293                             true /*shown*/, null /* animatorHandler */);
294                 }
295                 if (mController != null) {
296                     if (mLastAppearedTaskTargets != null) {
297                         for (RemoteAnimationTarget lastTarget : mLastAppearedTaskTargets) {
298                             for (RemoteAnimationTarget appearedTarget : appearedTaskTargets) {
299                                 if (lastTarget != null &&
300                                         appearedTarget.taskId != lastTarget.taskId) {
301                                     mController.removeTaskTarget(lastTarget.taskId);
302                                 }
303                             }
304                         }
305                     }
306                     mLastAppearedTaskTargets = appearedTaskTargets;
307                     mLastGestureState.updateLastAppearedTaskTargets(mLastAppearedTaskTargets);
308                 }
309             }
310 
311             @Override
312             public boolean onSwitchToScreenshot(Runnable onFinished) {
313                 if (!containerInterface.isInLiveTileMode()
314                         || containerInterface.getCreatedContainer() == null) {
315                     // No need to switch since tile is already a screenshot.
316                     onFinished.run();
317                 } else {
318                     final RecentsView recentsView =
319                             containerInterface.getCreatedContainer().getOverviewPanel();
320                     if (recentsView != null) {
321                         recentsView.switchToScreenshot(onFinished);
322                     } else {
323                         onFinished.run();
324                     }
325                 }
326                 return true;
327             }
328         });
329         final long eventTime = gestureState.getSwipeUpStartTimeMs();
330         mCallbacks.addListener(gestureState);
331         mCallbacks.addListener(listener);
332 
333         if (ENABLE_SHELL_TRANSITIONS) {
334             final ActivityOptions options = ActivityOptions.makeBasic();
335             options.setPendingIntentBackgroundActivityLaunchAllowedByPermission(true);
336             // Use regular (non-transient) launch for all apps page to control IME.
337             if (!containerInterface.allowAllAppsFromOverview()) {
338                 options.setTransientLaunch();
339             }
340             options.setSourceInfo(ActivityOptions.SourceInfo.TYPE_RECENTS_ANIMATION, eventTime);
341             mRecentsAnimationStartPending = getSystemUiProxy()
342                     .startRecentsActivity(intent, options, mCallbacks);
343             if (enableHandleDelayedGestureCallbacks()) {
344                 ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
345                         "TaskAnimationManager.startRecentsAnimation(shell transition path): ")
346                         .append("Setting mRecentsAnimationStartPending = ")
347                         .append(mRecentsAnimationStartPending));
348             }
349         } else {
350             UI_HELPER_EXECUTOR.execute(
351                     () -> ActivityManagerWrapper.getInstance().startRecentsActivity(
352                             intent,
353                             eventTime,
354                             mCallbacks,
355                             result -> {
356                                 if (enableHandleDelayedGestureCallbacks()) {
357                                     ActiveGestureLog.INSTANCE.addLog(
358                                             new ActiveGestureLog.CompoundString(
359                                                     "TaskAnimationManager.startRecentsAnimation")
360                                                     .append("(legacy path): Setting ")
361                                                     .append("mRecentsAnimationStartPending = ")
362                                                     .append(result));
363                                 }
364                                 mRecentsAnimationStartPending = result;
365                             },
366                             MAIN_EXECUTOR.getHandler()));
367         }
368         gestureState.setState(STATE_RECENTS_ANIMATION_INITIALIZED);
369         return mCallbacks;
370     }
371 
372     /**
373      * Continues the existing running recents animation for a new gesture.
374      */
continueRecentsAnimation(GestureState gestureState)375     public RecentsAnimationCallbacks continueRecentsAnimation(GestureState gestureState) {
376         ActiveGestureLog.INSTANCE.addLog(/* event= */ "continueRecentsAnimation");
377         mCallbacks.removeListener(mLastGestureState);
378         mLastGestureState = gestureState;
379         mCallbacks.addListener(gestureState);
380         gestureState.setState(STATE_RECENTS_ANIMATION_INITIALIZED
381                 | STATE_RECENTS_ANIMATION_STARTED);
382         gestureState.updateLastAppearedTaskTargets(mLastAppearedTaskTargets);
383         return mCallbacks;
384     }
385 
onSystemUiFlagsChanged(@uickStepContract.SystemUiStateFlags long lastSysUIFlags, @QuickStepContract.SystemUiStateFlags long newSysUIFlags)386     public void onSystemUiFlagsChanged(@QuickStepContract.SystemUiStateFlags long lastSysUIFlags,
387             @QuickStepContract.SystemUiStateFlags long newSysUIFlags) {
388         long isShadeExpandedFlagMask =
389                 SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED | SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
390         boolean wasExpanded = hasAnyFlag(lastSysUIFlags, isShadeExpandedFlagMask);
391         boolean isExpanded = hasAnyFlag(newSysUIFlags, isShadeExpandedFlagMask);
392         if (wasExpanded != isExpanded && isExpanded) {
393             // End live tile when expanding the notification panel for the first time from
394             // overview.
395             if (endLiveTile()) {
396                 return;
397             }
398         }
399 
400         boolean wasLocked = SystemUiFlagUtils.isLocked(lastSysUIFlags);
401         boolean isLocked = SystemUiFlagUtils.isLocked(newSysUIFlags);
402         if (wasLocked != isLocked && isLocked) {
403             // Finish the running recents animation when locking the device.
404             finishRunningRecentsAnimation(
405                     mController != null && mController.getFinishTargetIsLauncher());
406         }
407     }
408 
hasAnyFlag(long flags, long flagMask)409     private boolean hasAnyFlag(long flags, long flagMask) {
410         return (flags & flagMask) != 0;
411     }
412 
413     /**
414      * Switches the {@link RecentsView} to screenshot if in live tile mode.
415      *
416      * @return true iff the {@link RecentsView} was in live tile mode and was switched to screenshot
417      */
endLiveTile()418     public boolean endLiveTile() {
419         if (mLastGestureState == null) {
420             return false;
421         }
422         BaseContainerInterface containerInterface = mLastGestureState.getContainerInterface();
423         if (!containerInterface.isInLiveTileMode()
424                 || containerInterface.getCreatedContainer() == null) {
425             return false;
426         }
427         RecentsView recentsView = containerInterface.getCreatedContainer().getOverviewPanel();
428         if (recentsView == null) {
429             return false;
430         }
431         recentsView.switchToScreenshot(null, () -> recentsView.finishRecentsAnimation(
432                 true /* toRecents */, false /* shouldPip */, null));
433         return true;
434     }
435 
setLiveTileCleanUpHandler(Runnable cleanUpHandler)436     public void setLiveTileCleanUpHandler(Runnable cleanUpHandler) {
437         mLiveTileCleanUpHandler = cleanUpHandler;
438     }
439 
enableLiveTileRestartListener()440     public void enableLiveTileRestartListener() {
441         TaskStackChangeListeners.getInstance().registerTaskStackListener(mLiveTileRestartListener);
442     }
443 
444     /**
445      * Finishes the running recents animation.
446      */
finishRunningRecentsAnimation(boolean toHome)447     public void finishRunningRecentsAnimation(boolean toHome) {
448         finishRunningRecentsAnimation(toHome, false /* forceFinish */, null /* forceFinishCb */);
449     }
450 
451     /**
452      * Finishes the running recents animation.
453      * @param forceFinish will synchronously finish the controller
454      */
finishRunningRecentsAnimation(boolean toHome, boolean forceFinish, Runnable forceFinishCb)455     public void finishRunningRecentsAnimation(boolean toHome, boolean forceFinish,
456             Runnable forceFinishCb) {
457         if (mController != null) {
458             ActiveGestureLog.INSTANCE.addLog(
459                     /* event= */ "finishRunningRecentsAnimation", toHome);
460             if (forceFinish) {
461                 mController.finishController(toHome, forceFinishCb, false /* sendUserLeaveHint */,
462                         true /* forceFinish */);
463             } else {
464                 Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), toHome
465                         ? mController::finishAnimationToHome
466                         : mController::finishAnimationToApp);
467             }
468         }
469     }
470 
471     /**
472      * Used to notify a listener of the current recents animation state (used if the listener was
473      * not yet added to the callbacks at the point that the listener callbacks would have been
474      * made).
475      */
notifyRecentsAnimationState( RecentsAnimationCallbacks.RecentsAnimationListener listener)476     public void notifyRecentsAnimationState(
477             RecentsAnimationCallbacks.RecentsAnimationListener listener) {
478         if (isRecentsAnimationRunning()) {
479             listener.onRecentsAnimationStart(mController, mTargets);
480         }
481         // TODO: Do we actually need to report canceled/finished?
482     }
483 
484     /**
485      * @return whether there is a recents animation running.
486      */
isRecentsAnimationRunning()487     public boolean isRecentsAnimationRunning() {
488         return mController != null;
489     }
490 
491     /**
492      * Cleans up the recents animation entirely.
493      */
cleanUpRecentsAnimation(RecentsAnimationCallbacks targetCallbacks)494     private void cleanUpRecentsAnimation(RecentsAnimationCallbacks targetCallbacks) {
495         if (mCallbacks != targetCallbacks) {
496             ActiveGestureLog.INSTANCE.addLog(
497                     /* event= */ "cleanUpRecentsAnimation skipped due to wrong callbacks");
498             return;
499         }
500         ActiveGestureLog.INSTANCE.addLog(/* event= */ "cleanUpRecentsAnimation");
501         if (mLiveTileCleanUpHandler != null) {
502             mLiveTileCleanUpHandler.run();
503             mLiveTileCleanUpHandler = null;
504         }
505         TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mLiveTileRestartListener);
506 
507         // Release all the target leashes
508         if (mTargets != null) {
509             mTargets.release();
510         }
511 
512         // Clean up all listeners to ensure we don't get subsequent callbacks
513         if (mCallbacks != null) {
514             mCallbacks.removeAllListeners();
515         }
516 
517         mController = null;
518         mCallbacks = null;
519         mTargets = null;
520         mLastGestureState = null;
521         mLastAppearedTaskTargets = null;
522     }
523 
524     @Nullable
getCurrentCallbacks()525     public RecentsAnimationCallbacks getCurrentCallbacks() {
526         return mCallbacks;
527     }
528 
dump(String prefix, PrintWriter pw)529     public void dump(String prefix, PrintWriter pw) {
530         pw.println(prefix + "TaskAnimationManager:");
531 
532         if (enableHandleDelayedGestureCallbacks()) {
533             pw.println(prefix + "\tmRecentsAnimationStartPending=" + mRecentsAnimationStartPending);
534             pw.println(prefix + "\tmShouldIgnoreUpcomingGestures=" + mShouldIgnoreMotionEvents);
535         }
536         if (mController != null) {
537             mController.dump(prefix + '\t', pw);
538         }
539         if (mCallbacks != null) {
540             mCallbacks.dump(prefix + '\t', pw);
541         }
542         if (mTargets != null) {
543             mTargets.dump(prefix + '\t', pw);
544         }
545         if (mLastGestureState != null) {
546             mLastGestureState.dump(prefix + '\t', pw);
547         }
548     }
549 }
550