From 1c2d522d1b2112b5a2b8925868938c359fa09664 Mon Sep 17 00:00:00 2001 From: Winson Chung Date: Tue, 8 Jun 2021 11:50:13 -0700 Subject: [PATCH] Don't rely on intent to call back from activity tracker - The intent is not updated in certain cases which means that the callback may not be made if Launcher gets recreated. Instead have the tracker manage the set of registered callbacks. - This change allows AbsSwipeUpHandler to continue to receive onActivityInit calls even if Launcher restarts, and also to handle a case where restarting while waiting for a page-settling callback will continue to finish the gesture. Bug: 183962705 Test: Force recreate at various points in the gesture Change-Id: Ib5ead8c868e798e26e56776f57bd715c79d087cd --- .../hybridhotseat/HotseatEduActivity.java | 4 +- .../android/quickstep/AbsSwipeUpHandler.java | 12 ++- .../quickstep/OverviewCommandHelper.java | 5 +- .../android/quickstep/RecentsActivity.java | 2 +- .../OtherActivityInputConsumer.java | 4 +- .../quickstep/util/ActivityInitListener.java | 15 ++-- src/com/android/launcher3/Launcher.java | 2 +- .../launcher3/dragndrop/AddItemActivity.java | 2 +- .../launcher3/states/RotationHelper.java | 3 +- .../launcher3/util/ActivityTracker.java | 73 +++++++++---------- 10 files changed, 62 insertions(+), 60 deletions(-) diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduActivity.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduActivity.java index 3a1a2f7ae2..3a7d821ed6 100644 --- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduActivity.java +++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduActivity.java @@ -21,6 +21,7 @@ import android.content.Intent; import android.os.Bundle; import com.android.launcher3.BaseActivity; +import com.android.launcher3.Launcher; import com.android.launcher3.uioverrides.QuickstepLauncher; import com.android.launcher3.util.ActivityTracker; @@ -37,7 +38,8 @@ public class HotseatEduActivity extends Activity { .addCategory(Intent.CATEGORY_HOME) .setPackage(getPackageName()) .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - new HotseatActivityTracker<>().addToIntent(homeIntent); + + Launcher.ACTIVITY_TRACKER.registerCallback(new HotseatActivityTracker()); startActivity(homeIntent); finish(); } diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java index 52f34d90e0..44ac4ae139 100644 --- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java +++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java @@ -348,6 +348,13 @@ public abstract class AbsSwipeUpHandler, } if (mActivity != null) { + if (mStateCallback.hasStates(STATE_GESTURE_COMPLETED)) { + // If the activity has restarted between setting the page scroll settling callback + // and actually receiving the callback, just mark the gesture completed + mGestureState.setState(STATE_RECENTS_SCROLLING_FINISHED); + return true; + } + // The launcher may have been recreated as a result of device rotation. int oldState = mStateCallback.getState() & ~LAUNCHER_UI_STATES; initStateCallbacks(); @@ -1713,13 +1720,12 @@ public abstract class AbsSwipeUpHandler, /** * Registers a callback to run when the activity is ready. - * @param intent The intent that will be used to start the activity if it doesn't exist already. */ - public void initWhenReady(Intent intent) { + public void initWhenReady() { // Preload the plan RecentsModel.INSTANCE.get(mContext).getTasks(null); - mActivityInitListener.register(intent); + mActivityInitListener.register(); } /** diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java index dbdd75fc13..2beef0a7ce 100644 --- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java +++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java @@ -184,9 +184,7 @@ public class OverviewCommandHelper { .newHandler(gestureState, cmd.createTime); interactionHandler.setGestureEndCallback( () -> onTransitionComplete(cmd, interactionHandler)); - - Intent intent = new Intent(interactionHandler.getLaunchIntent()); - interactionHandler.initWhenReady(intent); + interactionHandler.initWhenReady(); RecentsAnimationListener recentAnimListener = new RecentsAnimationListener() { @Override @@ -212,6 +210,7 @@ public class OverviewCommandHelper { cmd.mActiveCallbacks.addListener(recentAnimListener); mTaskAnimationManager.notifyRecentsAnimationState(recentAnimListener); } else { + Intent intent = new Intent(interactionHandler.getLaunchIntent()); intent.putExtra(INTENT_EXTRA_LOG_TRACE_ID, gestureState.getGestureId()); cmd.mActiveCallbacks = mTaskAnimationManager.startRecentsAnimation( gestureState, intent, interactionHandler); diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java index 68526420ef..80c6a97a73 100644 --- a/quickstep/src/com/android/quickstep/RecentsActivity.java +++ b/quickstep/src/com/android/quickstep/RecentsActivity.java @@ -141,7 +141,7 @@ public final class RecentsActivity extends StatefulActivity { @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); - ACTIVITY_TRACKER.handleNewIntent(this, intent); + ACTIVITY_TRACKER.handleNewIntent(this); } /** diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java index 9878d4527a..725c7c45a5 100644 --- a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java +++ b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java @@ -389,8 +389,7 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC mInteractionHandler = mHandlerFactory.newHandler(mGestureState, touchTimeMs); mInteractionHandler.setGestureEndCallback(this::onInteractionGestureFinished); mMotionPauseDetector.setOnMotionPauseListener(mInteractionHandler.getMotionPauseListener()); - Intent intent = new Intent(mInteractionHandler.getLaunchIntent()); - mInteractionHandler.initWhenReady(intent); + mInteractionHandler.initWhenReady(); if (mTaskAnimationManager.isRecentsAnimationRunning()) { mActiveCallbacks = mTaskAnimationManager.continueRecentsAnimation(mGestureState); @@ -398,6 +397,7 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC mTaskAnimationManager.notifyRecentsAnimationState(mInteractionHandler); notifyGestureStarted(true /*isLikelyToStartNewTask*/); } else { + Intent intent = new Intent(mInteractionHandler.getLaunchIntent()); intent.putExtra(INTENT_EXTRA_LOG_TRACE_ID, mGestureState.getGestureId()); mActiveCallbacks = mTaskAnimationManager.startRecentsAnimation(mGestureState, intent, mInteractionHandler); diff --git a/quickstep/src/com/android/quickstep/util/ActivityInitListener.java b/quickstep/src/com/android/quickstep/util/ActivityInitListener.java index dfb8c1d548..b9879ab82b 100644 --- a/quickstep/src/com/android/quickstep/util/ActivityInitListener.java +++ b/quickstep/src/com/android/quickstep/util/ActivityInitListener.java @@ -26,7 +26,8 @@ import com.android.launcher3.util.ActivityTracker.SchedulerCallback; import java.util.function.BiPredicate; -public class ActivityInitListener implements SchedulerCallback { +public class ActivityInitListener implements + SchedulerCallback { private BiPredicate mOnInitListener; private final ActivityTracker mActivityTracker; @@ -47,6 +48,7 @@ public class ActivityInitListener implements SchedulerCa @Override public final boolean init(T activity, boolean alreadyOnHome) { if (!mIsRegistered) { + // Don't receive any more updates return false; } return handleInit(activity, alreadyOnHome); @@ -59,18 +61,17 @@ public class ActivityInitListener implements SchedulerCa /** * Registers the activity-created listener. If the activity is already created, then the * callback provided in the constructor will be called synchronously. - * @param intent The intent that will be used to initialize the activity, if the activity - * doesn't already exist. We add the callback as an extra on this intent. */ - public void register(Intent intent) { + public void register() { mIsRegistered = true; - mActivityTracker.runCallbackWhenActivityExists(this, intent); + 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; } @@ -82,9 +83,9 @@ public class ActivityInitListener implements SchedulerCa */ public void registerAndStartActivity(Intent intent, RemoteAnimationProvider animProvider, Context context, Handler handler, long duration) { - mIsRegistered = true; + register(); Bundle options = animProvider.toActivityOptions(handler, duration, context).toBundle(); - context.startActivity(addToIntent(new Intent(intent)), options); + context.startActivity(new Intent(intent), options); } } diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 578379b70d..c91fb27619 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -1472,7 +1472,7 @@ public class Launcher extends StatefulActivity implements Launche boolean shouldMoveToDefaultScreen = alreadyOnHome && isInState(NORMAL) && AbstractFloatingView.getTopOpenView(this) == null; boolean isActionMain = Intent.ACTION_MAIN.equals(intent.getAction()); - boolean internalStateHandled = ACTIVITY_TRACKER.handleNewIntent(this, intent); + boolean internalStateHandled = ACTIVITY_TRACKER.handleNewIntent(this); hideKeyboard(); if (isActionMain) { if (!internalStateHandled) { diff --git a/src/com/android/launcher3/dragndrop/AddItemActivity.java b/src/com/android/launcher3/dragndrop/AddItemActivity.java index b4288ce1f5..2844fb2178 100644 --- a/src/com/android/launcher3/dragndrop/AddItemActivity.java +++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java @@ -213,7 +213,7 @@ public class AddItemActivity extends BaseActivity .addCategory(Intent.CATEGORY_HOME) .setPackage(getPackageName()) .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - Launcher.ACTIVITY_TRACKER.runCallbackWhenActivityExists(listener, homeIntent); + Launcher.ACTIVITY_TRACKER.registerCallback(listener); startActivity(homeIntent, ActivityOptions.makeCustomAnimation(this, 0, android.R.anim.fade_out) .toBundle()); diff --git a/src/com/android/launcher3/states/RotationHelper.java b/src/com/android/launcher3/states/RotationHelper.java index 5832711f11..87871b11db 100644 --- a/src/com/android/launcher3/states/RotationHelper.java +++ b/src/com/android/launcher3/states/RotationHelper.java @@ -25,6 +25,7 @@ import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import com.android.launcher3.BaseActivity; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Utilities; +import com.android.launcher3.util.ActivityTracker; import com.android.launcher3.util.UiThreadHelper; /** @@ -50,7 +51,7 @@ public class RotationHelper implements OnSharedPreferenceChangeListener, /** * Rotation request made by - * {@link com.android.launcher3.util.ActivityTracker.SchedulerCallback}. + * {@link ActivityTracker.SchedulerCallback}. * This supersedes any other request. */ private int mStateHandlerRequest = REQUEST_NONE; diff --git a/src/com/android/launcher3/util/ActivityTracker.java b/src/com/android/launcher3/util/ActivityTracker.java index b5b9c2f43a..7af1a13d67 100644 --- a/src/com/android/launcher3/util/ActivityTracker.java +++ b/src/com/android/launcher3/util/ActivityTracker.java @@ -15,15 +15,14 @@ */ package com.android.launcher3.util; -import android.content.Intent; -import android.os.Bundle; -import android.os.IBinder; - 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; /** * Helper class to statically track activity creation @@ -32,8 +31,7 @@ import java.lang.ref.WeakReference; public final class ActivityTracker { private WeakReference mCurrentActivity = new WeakReference<>(null); - - private static final String EXTRA_SCHEDULER_CALLBACK = "launcher.scheduler_callback"; + private CopyOnWriteArrayList> mCallbacks = new CopyOnWriteArrayList<>(); @Nullable public R getCreatedActivity() { @@ -47,43 +45,50 @@ public final class ActivityTracker { } /** - * Call {@link SchedulerCallback#init(BaseActivity, boolean)} when the activity is ready. - * If the activity is already created, this is called immediately, otherwise we add the - * callback as an extra on the intent, and will call init() when we get handleIntent(). + * Call {@link SchedulerCallback#init(BaseActivity, boolean)} when the + * activity is ready. If the activity is already created, this is called immediately. + * + * 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. + * * @param callback The callback to call init() on when the activity is ready. - * @param intent The intent that will be used to initialize the activity, if the activity - * doesn't already exist. We add the callback as an extra on this intent. */ - public void runCallbackWhenActivityExists(SchedulerCallback callback, Intent intent) { + public void registerCallback(SchedulerCallback callback) { T activity = mCurrentActivity.get(); + mCallbacks.add(callback); if (activity != null) { - callback.init(activity, activity.isStarted()); - } else { - callback.addToIntent(intent); + if (!callback.init(activity, activity.isStarted())) { + unregisterCallback(callback); + } } } + /** + * Unregisters a registered callback. + */ + public void unregisterCallback(SchedulerCallback callback) { + mCallbacks.remove(callback); + } + public boolean handleCreate(T activity) { mCurrentActivity = new WeakReference<>(activity); - return handleIntent(activity, activity.getIntent(), false); + return handleIntent(activity, false /* alreadyOnHome */); } - public boolean handleNewIntent(T activity, Intent intent) { - return handleIntent(activity, intent, activity.isStarted()); + public boolean handleNewIntent(T activity) { + return handleIntent(activity, activity.isStarted()); } - private boolean handleIntent(T activity, Intent intent, boolean alreadyOnHome) { - if (intent != null && intent.getExtras() != null) { - IBinder stateBinder = intent.getExtras().getBinder(EXTRA_SCHEDULER_CALLBACK); - SchedulerCallback handler = ObjectWrapper.unwrap(stateBinder); - if (handler != null) { - if (!handler.init(activity, alreadyOnHome)) { - intent.getExtras().remove(EXTRA_SCHEDULER_CALLBACK); - } - return true; + private boolean handleIntent(T activity, boolean alreadyOnHome) { + boolean handled = false; + for (SchedulerCallback cb : mCallbacks) { + if (!cb.init(activity, alreadyOnHome)) { + // Callback doesn't want any more updates + unregisterCallback(cb); } + handled = true; } - return false; + return handled; } public interface SchedulerCallback { @@ -94,17 +99,5 @@ public final class ActivityTracker { * @return Whether to continue receiving callbacks (i.e. if the activity is recreated). */ boolean init(T activity, boolean alreadyOnHome); - - /** - * Adds this callback as an extra on the intent, so we can retrieve it in handleIntent() and - * call {@link #init}. The intent should be used to start the activity after calling this - * method in order for us to get handleIntent(). - */ - default Intent addToIntent(Intent intent) { - Bundle extras = new Bundle(); - extras.putBinder(EXTRA_SCHEDULER_CALLBACK, ObjectWrapper.wrap(this)); - intent.putExtras(extras); - return intent; - } } }