Merge "Cancel gestures on launcher destroy" into tm-qpr-dev
diff --git a/quickstep/src/com/android/launcher3/LauncherInitListener.java b/quickstep/src/com/android/launcher3/LauncherLifecycleListener.java
similarity index 78%
rename from quickstep/src/com/android/launcher3/LauncherInitListener.java
rename to quickstep/src/com/android/launcher3/LauncherLifecycleListener.java
index 28bd701..82c646d 100644
--- a/quickstep/src/com/android/launcher3/LauncherInitListener.java
+++ b/quickstep/src/com/android/launcher3/LauncherLifecycleListener.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,19 +16,23 @@
 package com.android.launcher3;
 
 import android.animation.AnimatorSet;
+import android.annotation.Nullable;
 import android.annotation.TargetApi;
 import android.os.Build;
 import android.os.CancellationSignal;
 import android.view.RemoteAnimationTarget;
 
 import com.android.launcher3.uioverrides.QuickstepLauncher;
-import com.android.quickstep.util.ActivityInitListener;
+import com.android.quickstep.util.ActivityLifecycleListener;
 import com.android.quickstep.util.RemoteAnimationProvider;
 
 import java.util.function.BiPredicate;
 
+/**
+ * {@link ActivityLifecycleListener} for the in-launcher recents.
+ */
 @TargetApi(Build.VERSION_CODES.P)
-public class LauncherInitListener extends ActivityInitListener<Launcher> {
+public class LauncherLifecycleListener extends ActivityLifecycleListener<Launcher> {
 
     private RemoteAnimationProvider mRemoteAnimationProvider;
 
@@ -36,13 +40,16 @@
      * @param onInitListener a callback made when the activity is initialized. The callback should
      *                       return true to continue receiving callbacks (ie. for if the activity is
      *                       recreated).
+     * @param onDestroyListener a callback made when the activity is destroyed.
      */
-    public LauncherInitListener(BiPredicate<Launcher, Boolean> onInitListener) {
-        super(onInitListener, Launcher.ACTIVITY_TRACKER);
+    public LauncherLifecycleListener(
+            @Nullable BiPredicate<Launcher, Boolean> onInitListener,
+            @Nullable Runnable onDestroyListener) {
+        super(onInitListener, onDestroyListener, Launcher.ACTIVITY_TRACKER);
     }
 
     @Override
-    public boolean handleInit(Launcher launcher, boolean alreadyOnHome) {
+    public boolean handleActivityReady(Launcher launcher, boolean alreadyOnHome) {
         if (mRemoteAnimationProvider != null) {
             QuickstepTransitionManager appTransitionManager =
                     ((QuickstepLauncher) launcher).getAppTransitionManager();
@@ -68,7 +75,7 @@
             }, cancellationSignal);
         }
         launcher.deferOverlayCallbacksUntilNextResumeOrStop();
-        return super.handleInit(launcher, alreadyOnHome);
+        return super.handleActivityReady(launcher, alreadyOnHome);
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 3d8ffc4..e41841a 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -50,6 +50,7 @@
 import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.CANCEL_RECENTS_ANIMATION;
 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.EXPECTING_TASK_APPEARED;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.LAUNCHER_DESTROYED;
 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.ON_SETTLED_ON_END_TARGET;
 import static com.android.quickstep.util.VibratorWrapper.OVERVIEW_HAPTIC;
 import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
@@ -113,7 +114,7 @@
 import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle;
 import com.android.quickstep.util.ActiveGestureErrorDetector;
 import com.android.quickstep.util.ActiveGestureLog;
-import com.android.quickstep.util.ActivityInitListener;
+import com.android.quickstep.util.ActivityLifecycleListener;
 import com.android.quickstep.util.AnimatorControllerWithResistance;
 import com.android.quickstep.util.InputConsumerProxy;
 import com.android.quickstep.util.InputProxyHandlerFactory;
@@ -159,7 +160,7 @@
 
     protected final BaseActivityInterface<S, T> mActivityInterface;
     protected final InputConsumerProxy mInputConsumerProxy;
-    protected final ActivityInitListener mActivityInitListener;
+    protected final ActivityLifecycleListener mActivityInitListener;
     // Callbacks to be made once the recents animation starts
     private final ArrayList<Runnable> mRecentsAnimationStartCallbacks = new ArrayList<>();
     private final OnScrollChangedListener mOnRecentsScrollListener = this::onRecentsViewScroll;
@@ -329,7 +330,8 @@
             InputConsumerController inputConsumer) {
         super(context, deviceState, gestureState);
         mActivityInterface = gestureState.getActivityInterface();
-        mActivityInitListener = mActivityInterface.createActivityInitListener(this::onActivityInit);
+        mActivityInitListener = mActivityInterface.createActivityLifecycleListener(
+                this::onActivityInit, this::onActivityDestroy);
         mInputConsumerProxy =
                 new InputConsumerProxy(context, /* rotationSupplier = */ () -> {
                     if (mRecentsView == null) {
@@ -520,6 +522,11 @@
         return true;
     }
 
+    private void onActivityDestroy() {
+        ActiveGestureLog.INSTANCE.addLog("Launcher activity destroyed", LAUNCHER_DESTROYED);
+        onGestureCancelled();
+    }
+
     /**
      * Return true if the window should be translated horizontally if the recents view scrolls
      */
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index 274b686..5523518 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -60,7 +60,7 @@
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.NavigationMode;
 import com.android.launcher3.views.ScrimView;
-import com.android.quickstep.util.ActivityInitListener;
+import com.android.quickstep.util.ActivityLifecycleListener;
 import com.android.quickstep.util.AnimatorControllerWithResistance;
 import com.android.quickstep.views.RecentsView;
 import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -123,8 +123,17 @@
     public abstract AnimationFactory prepareRecentsUI(RecentsAnimationDeviceState deviceState,
             boolean activityVisible, Consumer<AnimatorControllerWithResistance> callback);
 
-    public abstract ActivityInitListener createActivityInitListener(
-            Predicate<Boolean> onInitListener);
+    /**
+     * Creates a activity listener for activity initialized and/or destroyed. One or both of these
+     * listeners must be provided.
+     *
+     * @param onInitListener a callback made when the activity is initialized. The callback should
+     *                       return true to continue receiving callbacks (ie. for if the activity is
+     *                       recreated).
+     * @param onDestroyListener a callback made when the activity is destroyed.
+     */
+    public abstract ActivityLifecycleListener createActivityLifecycleListener(
+            @Nullable Predicate<Boolean> onInitListener, @Nullable Runnable onDestroyListener);
 
     /**
      * Sets a callback to be run when an activity launch happens while launcher is not yet resumed.
diff --git a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
index ae9fb0b..a658566 100644
--- a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
@@ -36,7 +36,7 @@
 import com.android.launcher3.util.DisplayController;
 import com.android.quickstep.GestureState.GestureEndTarget;
 import com.android.quickstep.fallback.RecentsState;
-import com.android.quickstep.util.ActivityInitListener;
+import com.android.quickstep.util.ActivityLifecycleListener;
 import com.android.quickstep.util.AnimatorControllerWithResistance;
 import com.android.quickstep.views.RecentsView;
 
@@ -88,10 +88,12 @@
     }
 
     @Override
-    public ActivityInitListener createActivityInitListener(
-            Predicate<Boolean> onInitListener) {
-        return new ActivityInitListener<>((activity, alreadyOnHome) ->
-                onInitListener.test(alreadyOnHome), RecentsActivity.ACTIVITY_TRACKER);
+    public ActivityLifecycleListener createActivityLifecycleListener(
+            @Nullable Predicate<Boolean> onInitListener, @Nullable Runnable onDestroyListener) {
+        return new ActivityLifecycleListener<>(
+                (activity, alreadyOnHome) -> onInitListener.test(alreadyOnHome),
+                onDestroyListener,
+                RecentsActivity.ACTIVITY_TRACKER);
     }
 
     @Nullable
diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index 9ff9416..cb54d2e 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -37,7 +37,7 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAnimUtils;
-import com.android.launcher3.LauncherInitListener;
+import com.android.launcher3.LauncherLifecycleListener;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.statehandlers.DepthController;
@@ -49,7 +49,7 @@
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.NavigationMode;
 import com.android.quickstep.GestureState.GestureEndTarget;
-import com.android.quickstep.util.ActivityInitListener;
+import com.android.quickstep.util.ActivityLifecycleListener;
 import com.android.quickstep.util.AnimatorControllerWithResistance;
 import com.android.quickstep.util.LayoutUtils;
 import com.android.quickstep.views.RecentsView;
@@ -136,9 +136,10 @@
     }
 
     @Override
-    public ActivityInitListener createActivityInitListener(Predicate<Boolean> onInitListener) {
-        return new LauncherInitListener((activity, alreadyOnHome) ->
-                onInitListener.test(alreadyOnHome));
+    public ActivityLifecycleListener createActivityLifecycleListener(
+            @Nullable Predicate<Boolean> onInitListener, @Nullable Runnable onDestroyListener) {
+        return new LauncherLifecycleListener((activity, alreadyOnHome) ->
+                onInitListener.test(alreadyOnHome), onDestroyListener);
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java b/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
index 60065fb..0fdd8b5 100644
--- a/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
+++ b/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
@@ -37,7 +37,7 @@
         ON_SETTLED_ON_END_TARGET, START_RECENTS_ANIMATION, FINISH_RECENTS_ANIMATION,
         CANCEL_RECENTS_ANIMATION, SET_ON_PAGE_TRANSITION_END_CALLBACK, CANCEL_CURRENT_ANIMATION,
         CLEANUP_SCREENSHOT, SCROLLER_ANIMATION_ABORTED, TASK_APPEARED, EXPECTING_TASK_APPEARED,
-        FLAG_USING_OTHER_ACTIVITY_INPUT_CONSUMER,
+        FLAG_USING_OTHER_ACTIVITY_INPUT_CONSUMER, LAUNCHER_DESTROYED,
 
         /**
          * These GestureEvents are specifically associated to state flags that get set in
@@ -162,6 +162,13 @@
                                         + "before/without setting end target to new task",
                                 writer);
                         break;
+                    case LAUNCHER_DESTROYED:
+                        errorDetected |= printErrorIfTrue(
+                                true,
+                                prefix,
+                                /* errorMessage= */ "Launcher destroyed mid-gesture",
+                                writer);
+                        break;
                     case STATE_GESTURE_COMPLETED:
                         errorDetected |= printErrorIfTrue(
                                 !encounteredEvents.contains(GestureEvent.MOTION_UP),
diff --git a/quickstep/src/com/android/quickstep/util/ActivityInitListener.java b/quickstep/src/com/android/quickstep/util/ActivityInitListener.java
deleted file mode 100644
index aeec36f..0000000
--- a/quickstep/src/com/android/quickstep/util/ActivityInitListener.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep.util;
-
-import com.android.launcher3.BaseActivity;
-import com.android.launcher3.util.ActivityTracker;
-import com.android.launcher3.util.ActivityTracker.SchedulerCallback;
-
-import java.util.function.BiPredicate;
-
-public class ActivityInitListener<T extends BaseActivity> implements
-        SchedulerCallback<T> {
-
-    private BiPredicate<T, Boolean> mOnInitListener;
-    private final ActivityTracker<T> mActivityTracker;
-
-    private boolean mIsRegistered = false;
-
-    /**
-     * @param onInitListener a callback made when the activity is initialized. The callback should
-     *                       return true to continue receiving callbacks (ie. for if the activity is
-     *                       recreated).
-     */
-    public ActivityInitListener(BiPredicate<T, Boolean> onInitListener,
-            ActivityTracker<T> tracker) {
-        mOnInitListener = onInitListener;
-        mActivityTracker = tracker;
-    }
-
-    @Override
-    public final boolean init(T activity, boolean alreadyOnHome) {
-        if (!mIsRegistered) {
-            // Don't receive any more updates
-            return false;
-        }
-        return handleInit(activity, alreadyOnHome);
-    }
-
-    protected boolean handleInit(T activity, boolean alreadyOnHome) {
-        return mOnInitListener.test(activity, alreadyOnHome);
-    }
-
-    /**
-     * Registers the activity-created listener. If the activity is already created, then the
-     * callback provided in the constructor will be called synchronously.
-     */
-    public void register() {
-        mIsRegistered = true;
-        mActivityTracker.registerCallback(this);
-    }
-
-    /**
-     * After calling this, we won't {@link #init} even when the activity is ready.
-     */
-    public void unregister() {
-        mActivityTracker.unregisterCallback(this);
-        mIsRegistered = false;
-        mOnInitListener = null;
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/util/ActivityLifecycleListener.java b/quickstep/src/com/android/quickstep/util/ActivityLifecycleListener.java
new file mode 100644
index 0000000..1edf188
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/ActivityLifecycleListener.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.util;
+
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.BaseActivity;
+import com.android.launcher3.util.ActivityTracker;
+import com.android.launcher3.util.ActivityTracker.SchedulerCallback;
+
+import java.util.function.BiPredicate;
+
+/**
+ * Listener for activity initialized and/or destroyed.
+ */
+public class ActivityLifecycleListener<T extends BaseActivity> implements
+        SchedulerCallback<T> {
+
+    private static final String TAG = "ActivityLifecycleListener";
+
+    @Nullable private final BiPredicate<T, Boolean> mOnInitListener;
+    @Nullable private final Runnable mOnDestroyListener;
+    private final ActivityTracker<T> mActivityTracker;
+
+    private boolean mIsRegistered = false;
+
+    /**
+     * One or both of {@code onInitListener} and {@code onInitListener} must be provided, otherwise
+     * the created instance will effectively be a no-op.
+     *
+     * @param onInitListener a callback made when the activity is initialized. The callback should
+     *                       return true to continue receiving callbacks (ie. for if the activity is
+     *                       recreated).
+     * @param onDestroyListener a callback made when the activity is destroyed.
+     */
+    public ActivityLifecycleListener(
+            @Nullable BiPredicate<T, Boolean> onInitListener,
+            @Nullable Runnable onDestroyListener,
+            ActivityTracker<T> tracker) {
+        if (onInitListener == null && onDestroyListener == null) {
+            throw new IllegalArgumentException("Both listeners cannot be null");
+        }
+        mOnInitListener = onInitListener;
+        mOnDestroyListener = onDestroyListener;
+        mActivityTracker = tracker;
+    }
+
+    @Override
+    public final boolean onActivityReady(T activity, boolean alreadyOnHome) {
+        if (!mIsRegistered) {
+            // Don't receive any more updates
+            return false;
+        }
+        return handleActivityReady(activity, alreadyOnHome);
+    }
+
+    protected boolean handleActivityReady(T activity, boolean alreadyOnHome) {
+        if (mOnInitListener == null) {
+            Log.e(TAG, "Cannot handle init: init listener is null", new Exception());
+            return false;
+        }
+        return mOnInitListener.test(activity, alreadyOnHome);
+    }
+
+    @Override
+    public void onActivityDestroyed() {
+        if (mOnDestroyListener == null) {
+            Log.e(TAG, "Cannot clean up: destroy listener is null", new Exception());
+            return;
+        }
+        mOnDestroyListener.run();
+    }
+
+    /**
+     * Registers the activity-created listener. If the activity is already created, then the
+     * callback provided in the constructor will be called synchronously.
+     */
+    public void register() {
+        mIsRegistered = true;
+        mActivityTracker.registerCallback(this, getType());
+    }
+
+    /**
+     * After calling this, we won't call {@link #onActivityReady} even when the activity is ready.
+     */
+    public void unregister() {
+        mActivityTracker.unregisterCallback(this, getType());
+        mIsRegistered = false;
+    }
+
+    private int getType() {
+        return mOnInitListener != null && mOnDestroyListener != null
+                ? ActivityTracker.TYPE_BOTH
+                : (mOnInitListener != null
+                        ? ActivityTracker.TYPE_INIT
+                        : ActivityTracker.TYPE_DESTROY);
+    }
+}
diff --git a/src/com/android/launcher3/dragndrop/BaseItemDragListener.java b/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
index 981e3a6..6d127b3 100644
--- a/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
+++ b/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
@@ -74,7 +74,7 @@
     }
 
     @Override
-    public boolean init(Launcher launcher, boolean alreadyOnHome) {
+    public boolean onActivityReady(Launcher launcher, boolean alreadyOnHome) {
         AbstractFloatingView.closeAllOpenViews(launcher, alreadyOnHome);
         launcher.getStateManager().goToState(NORMAL, alreadyOnHome /* animated */);
         launcher.getDragLayer().setOnDragListener(this);
diff --git a/src/com/android/launcher3/dragndrop/PinItemDragListener.java b/src/com/android/launcher3/dragndrop/PinItemDragListener.java
index af43ae8..fb924c1 100644
--- a/src/com/android/launcher3/dragndrop/PinItemDragListener.java
+++ b/src/com/android/launcher3/dragndrop/PinItemDragListener.java
@@ -72,8 +72,8 @@
     }
 
     @Override
-    public boolean init(Launcher launcher, boolean alreadyOnHome) {
-        super.init(launcher, alreadyOnHome);
+    public boolean onActivityReady(Launcher launcher, boolean alreadyOnHome) {
+        super.onActivityReady(launcher, alreadyOnHome);
         if (!alreadyOnHome) {
             launcher.useFadeOutAnimationForLauncherStart(mCancelSignal);
         }
diff --git a/src/com/android/launcher3/util/ActivityTracker.java b/src/com/android/launcher3/util/ActivityTracker.java
index 7af1a13..5f93a66 100644
--- a/src/com/android/launcher3/util/ActivityTracker.java
+++ b/src/com/android/launcher3/util/ActivityTracker.java
@@ -20,8 +20,6 @@
 import com.android.launcher3.BaseActivity;
 
 import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.HashSet;
 import java.util.concurrent.CopyOnWriteArrayList;
 
 /**
@@ -30,8 +28,15 @@
  */
 public final class ActivityTracker<T extends BaseActivity> {
 
+    public static final int TYPE_INIT = 0;
+    public static final int TYPE_DESTROY = 1;
+    public static final int TYPE_BOTH = 2;
+
     private WeakReference<T> mCurrentActivity = new WeakReference<>(null);
-    private CopyOnWriteArrayList<SchedulerCallback<T>> mCallbacks = new CopyOnWriteArrayList<>();
+    private CopyOnWriteArrayList<SchedulerCallback<T>> mActivityReadyCallbacks =
+            new CopyOnWriteArrayList<>();
+    private CopyOnWriteArrayList<SchedulerCallback<T>> mActivityDestroyedCallbacks =
+            new CopyOnWriteArrayList<>();
 
     @Nullable
     public <R extends T> R getCreatedActivity() {
@@ -42,32 +47,74 @@
         if (mCurrentActivity.get() == activity) {
             mCurrentActivity.clear();
         }
+        for (SchedulerCallback<T> cb : mActivityDestroyedCallbacks) {
+            cb.onActivityDestroyed();
+            unregisterCallback(cb, TYPE_DESTROY);
+        }
+    }
+
+    /** Registers an activity create callback. */
+    public void registerCallback(SchedulerCallback<T> callback) {
+        registerCallback(callback, TYPE_INIT);
     }
 
     /**
-     * Call {@link SchedulerCallback#init(BaseActivity, boolean)} when the
-     * activity is ready. If the activity is already created, this is called immediately.
+     * Call {@link SchedulerCallback#onActivityReady(BaseActivity, boolean)} when the
+     * activity is ready and/or {@link SchedulerCallback#onActivityDestroyed()} when the activity
+     * is destroyed.
      *
-     * The tracker maintains a strong ref to the callback, so it is up to the caller to return
-     * {@code false} in the callback OR to unregister the callback explicitly.
+     * If type is {@link ActivityTracker#TYPE_INIT} TYPE_INIT or
+     * {@link ActivityTracker#TYPE_BOTH} and the activity is already created, this
+     * {@link SchedulerCallback#onActivityReady(BaseActivity, boolean)} is called immediately.
      *
-     * @param callback The callback to call init() on when the activity is ready.
+     * If type is {@link ActivityTracker#TYPE_DESTROY} or
+     * {@link ActivityTracker#TYPE_BOTH} and the activity is already destroyed,
+     * {@link SchedulerCallback#onActivityDestroyed()} is called immediately.
+     *
+     * The tracker maintains a strong ref to the callbacks, so it is up to the caller to return
+     * {@code false} in {@link SchedulerCallback#onActivityReady(BaseActivity, boolean)} OR to
+     * unregister the callback explicitly.
+     *
+     * @param callback The callback to call init() or cleanUp() on when the activity is ready or
+     *                 destroyed.
+     * @param type whether to use this callback on activity create, destroy or both.
      */
-    public void registerCallback(SchedulerCallback<T> callback) {
+    public void registerCallback(SchedulerCallback<T> callback, int type) {
         T activity = mCurrentActivity.get();
-        mCallbacks.add(callback);
-        if (activity != null) {
-            if (!callback.init(activity, activity.isStarted())) {
-                unregisterCallback(callback);
+        if (type == TYPE_INIT || type == TYPE_BOTH) {
+            mActivityReadyCallbacks.add(callback);
+            if (activity != null) {
+                if (!callback.onActivityReady(activity, activity.isStarted())) {
+                    unregisterCallback(callback, TYPE_INIT);
+                }
+            }
+        }
+        if (type == TYPE_DESTROY || type == TYPE_BOTH) {
+            mActivityDestroyedCallbacks.add(callback);
+            if (activity == null) {
+                callback.onActivityDestroyed();
+                unregisterCallback(callback, TYPE_DESTROY);
             }
         }
     }
 
     /**
-     * Unregisters a registered callback.
+     * Unregisters a registered activity create callback.
      */
     public void unregisterCallback(SchedulerCallback<T> callback) {
-        mCallbacks.remove(callback);
+        unregisterCallback(callback, TYPE_INIT);
+    }
+
+    /**
+     * Unregisters a registered callback.
+     */
+    public void unregisterCallback(SchedulerCallback<T> callback, int type) {
+        if (type == TYPE_INIT || type == TYPE_BOTH) {
+            mActivityReadyCallbacks.remove(callback);
+        }
+        if (type == TYPE_DESTROY || type == TYPE_BOTH) {
+            mActivityDestroyedCallbacks.remove(callback);
+        }
     }
 
     public boolean handleCreate(T activity) {
@@ -81,8 +128,8 @@
 
     private boolean handleIntent(T activity, boolean alreadyOnHome) {
         boolean handled = false;
-        for (SchedulerCallback<T> cb : mCallbacks) {
-            if (!cb.init(activity, alreadyOnHome)) {
+        for (SchedulerCallback<T> cb : mActivityReadyCallbacks) {
+            if (!cb.onActivityReady(activity, alreadyOnHome)) {
                 // Callback doesn't want any more updates
                 unregisterCallback(cb);
             }
@@ -98,6 +145,11 @@
          * @param alreadyOnHome Whether the activity is already started.
          * @return Whether to continue receiving callbacks (i.e. if the activity is recreated).
          */
-        boolean init(T activity, boolean alreadyOnHome);
+        boolean onActivityReady(T activity, boolean alreadyOnHome);
+
+        /**
+         * Called then the activity gets destroyed.
+         */
+        default void onActivityDestroyed() { }
     }
 }