From 65dda6d03e63267c69d89fea06c25f9612fd2b59 Mon Sep 17 00:00:00 2001 From: Schneider Victor-tulias Date: Mon, 28 Nov 2022 14:22:20 -0800 Subject: [PATCH] Cancel gestures on launcher destroy A wide variety of gesture navigation bugs were caused if launcher happened to be recreated mid-gesture and we didn't handle it gracefully. Updated lifecycle callbacks to immediately cancel the ongoing gesture if launcher is destroyed. Fixes: 244593270 Fixes: 257976590 Test: forcefully(programmatically) recreate on every other gesture nav handle touch down; check that following gestures are not broken and animations are not frozen Change-Id: Ia8e936e26a42cfe26fc6bcd9eefb25d37bc08749 --- ...er.java => LauncherLifecycleListener.java} | 21 ++-- .../android/quickstep/AbsSwipeUpHandler.java | 13 +- .../quickstep/BaseActivityInterface.java | 15 ++- .../quickstep/FallbackActivityInterface.java | 12 +- .../quickstep/LauncherActivityInterface.java | 11 +- .../util/ActiveGestureErrorDetector.java | 9 +- .../quickstep/util/ActivityInitListener.java | 73 ----------- .../util/ActivityLifecycleListener.java | 113 ++++++++++++++++++ .../dragndrop/BaseItemDragListener.java | 2 +- .../dragndrop/PinItemDragListener.java | 4 +- .../launcher3/util/ActivityTracker.java | 88 +++++++++++--- 11 files changed, 243 insertions(+), 118 deletions(-) rename quickstep/src/com/android/launcher3/{LauncherInitListener.java => LauncherLifecycleListener.java} (78%) delete mode 100644 quickstep/src/com/android/quickstep/util/ActivityInitListener.java create mode 100644 quickstep/src/com/android/quickstep/util/ActivityLifecycleListener.java 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 28bd701a48..82c646d89c 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 { +public class LauncherLifecycleListener extends ActivityLifecycleListener { private RemoteAnimationProvider mRemoteAnimationProvider; @@ -36,13 +40,16 @@ public class LauncherInitListener extends ActivityInitListener { * @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 onInitListener) { - super(onInitListener, Launcher.ACTIVITY_TRACKER); + public LauncherLifecycleListener( + @Nullable BiPredicate 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 @@ public class LauncherInitListener extends ActivityInitListener { }, 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 9aedbf8517..7a14e81fe0 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.GestureState.STATE_RECENTS_SCROLLING_FINISHE 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.GestureState.GestureEndTarget; 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 @@ public abstract class AbsSwipeUpHandler, protected final BaseActivityInterface 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 mRecentsAnimationStartCallbacks = new ArrayList<>(); private final OnScrollChangedListener mOnRecentsScrollListener = this::onRecentsViewScroll; @@ -328,7 +329,8 @@ public abstract class AbsSwipeUpHandler, 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) { @@ -511,6 +513,11 @@ public abstract class AbsSwipeUpHandler, 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 274b686ea4..55235185ef 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.touch.PagedOrientationHandler; 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 class BaseActivityInterface callback); - public abstract ActivityInitListener createActivityInitListener( - Predicate 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 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 ae9fb0b385..a658566496 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.touch.PagedOrientationHandler; 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 @@ public final class FallbackActivityInterface extends } @Override - public ActivityInitListener createActivityInitListener( - Predicate onInitListener) { - return new ActivityInitListener<>((activity, alreadyOnHome) -> - onInitListener.test(alreadyOnHome), RecentsActivity.ACTIVITY_TRACKER); + public ActivityLifecycleListener createActivityLifecycleListener( + @Nullable Predicate 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 9ff941671b..cb54d2e8c2 100644 --- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java +++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java @@ -37,7 +37,7 @@ import androidx.annotation.UiThread; 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.uioverrides.QuickstepLauncher; 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 @@ public final class LauncherActivityInterface extends } @Override - public ActivityInitListener createActivityInitListener(Predicate onInitListener) { - return new LauncherInitListener((activity, alreadyOnHome) -> - onInitListener.test(alreadyOnHome)); + public ActivityLifecycleListener createActivityLifecycleListener( + @Nullable Predicate 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 60065fb16c..0fdd8b5e36 100644 --- a/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java +++ b/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java @@ -37,7 +37,7 @@ public class ActiveGestureErrorDetector { 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 @@ public class ActiveGestureErrorDetector { + "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 aeec36f63c..0000000000 --- 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 implements - SchedulerCallback { - - private BiPredicate mOnInitListener; - private final ActivityTracker 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 onInitListener, - ActivityTracker 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 0000000000..1edf1888e8 --- /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 implements + SchedulerCallback { + + private static final String TAG = "ActivityLifecycleListener"; + + @Nullable private final BiPredicate mOnInitListener; + @Nullable private final Runnable mOnDestroyListener; + private final ActivityTracker 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 onInitListener, + @Nullable Runnable onDestroyListener, + ActivityTracker 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 981e3a65a4..6d127b3998 100644 --- a/src/com/android/launcher3/dragndrop/BaseItemDragListener.java +++ b/src/com/android/launcher3/dragndrop/BaseItemDragListener.java @@ -74,7 +74,7 @@ public abstract class BaseItemDragListener implements View.OnDragListener, DragS } @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 af43ae83e2..fb924c14d1 100644 --- a/src/com/android/launcher3/dragndrop/PinItemDragListener.java +++ b/src/com/android/launcher3/dragndrop/PinItemDragListener.java @@ -72,8 +72,8 @@ public class PinItemDragListener extends BaseItemDragListener { } @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 7af1a13d67..5f93a66d03 100644 --- a/src/com/android/launcher3/util/ActivityTracker.java +++ b/src/com/android/launcher3/util/ActivityTracker.java @@ -20,8 +20,6 @@ import androidx.annotation.Nullable; 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 @@ import java.util.concurrent.CopyOnWriteArrayList; */ public final class ActivityTracker { + public static final int TYPE_INIT = 0; + public static final int TYPE_DESTROY = 1; + public static final int TYPE_BOTH = 2; + private WeakReference mCurrentActivity = new WeakReference<>(null); - private CopyOnWriteArrayList> mCallbacks = new CopyOnWriteArrayList<>(); + private CopyOnWriteArrayList> mActivityReadyCallbacks = + new CopyOnWriteArrayList<>(); + private CopyOnWriteArrayList> mActivityDestroyedCallbacks = + new CopyOnWriteArrayList<>(); @Nullable public R getCreatedActivity() { @@ -42,32 +47,74 @@ public final class ActivityTracker { if (mCurrentActivity.get() == activity) { mCurrentActivity.clear(); } + for (SchedulerCallback cb : mActivityDestroyedCallbacks) { + cb.onActivityDestroyed(); + unregisterCallback(cb, TYPE_DESTROY); + } + } + + /** Registers an activity create callback. */ + public void registerCallback(SchedulerCallback 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 callback) { + public void registerCallback(SchedulerCallback 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 callback) { - mCallbacks.remove(callback); + unregisterCallback(callback, TYPE_INIT); + } + + /** + * Unregisters a registered callback. + */ + public void unregisterCallback(SchedulerCallback 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 @@ public final class ActivityTracker { private boolean handleIntent(T activity, boolean alreadyOnHome) { boolean handled = false; - for (SchedulerCallback cb : mCallbacks) { - if (!cb.init(activity, alreadyOnHome)) { + for (SchedulerCallback cb : mActivityReadyCallbacks) { + if (!cb.onActivityReady(activity, alreadyOnHome)) { // Callback doesn't want any more updates unregisterCallback(cb); } @@ -98,6 +145,11 @@ public final class ActivityTracker { * @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() { } } }