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