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
This commit is contained in:
Schneider Victor-tulias
2022-11-28 14:22:20 -08:00
parent 691c6e511f
commit 65dda6d03e
11 changed files with 243 additions and 118 deletions

View File

@@ -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<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 @@ public final class ActivityTracker<T extends BaseActivity> {
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 @@ public final class ActivityTracker<T extends BaseActivity> {
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 @@ public final class ActivityTracker<T extends BaseActivity> {
* @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() { }
}
}