Merge "Use aconfig flag for desktop mode" into main
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index 7c6d7a8..ace2210 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -48,3 +48,10 @@
     description: "Enables taskbar with no recreation from lifecycle changes of TaskbarActivityContext."
     bug: "299193589"
 }
+
+flag {
+    name: "enable_home_transition_listener"
+    namespace: "launcher"
+    description: "Enables launcher to listen to all transitions that include home activity"
+    bug: "306053414"
+}
diff --git a/protos/launcher_atom.proto b/protos/launcher_atom.proto
index f8b08f8..dde69e3 100644
--- a/protos/launcher_atom.proto
+++ b/protos/launcher_atom.proto
@@ -135,7 +135,7 @@
   }
 }
 
-// Next value 52
+// Next value 53
 enum Attribute {
   option allow_alias = true;
 
@@ -188,6 +188,7 @@
   ALL_APPS_SEARCH_RESULT_EDUCARD = 43;
   ALL_APPS_SEARCH_RESULT_LOCATION = 50;
   ALL_APPS_SEARCH_RESULT_TEXT_HEADER = 51;
+  ALL_APPS_SEARCH_RESULT_NO_FULFILLMENT = 52;
 
   // Result sources
   DATA_SOURCE_APPSEARCH_APP_PREVIEW = 45;
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index c5d0b95..e77d2c6 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -1057,9 +1057,9 @@
         boolean allowBlurringLauncher = mLauncher.getStateManager().getState() != OVERVIEW
                 && BlurUtils.supportsBlursOnWindows();
 
-        ObjectAnimator backgroundRadiusAnim =
-                ObjectAnimator.ofFloat(mLauncher.getDepthController().stateDepth,
-                                MULTI_PROPERTY_VALUE, BACKGROUND_APP.getDepth(mLauncher))
+        LaunchDepthController depthController = new LaunchDepthController(mLauncher);
+        ObjectAnimator backgroundRadiusAnim = ObjectAnimator.ofFloat(depthController.stateDepth,
+                        MULTI_PROPERTY_VALUE, BACKGROUND_APP.getDepth(mLauncher))
                 .setDuration(APP_LAUNCH_DURATION);
 
         if (allowBlurringLauncher) {
@@ -1085,6 +1085,9 @@
                     new SurfaceControl.Transaction().remove(dimLayer).apply()));
         }
 
+        backgroundRadiusAnim.addListener(
+                AnimatorListeners.forEndCallback(depthController::dispose));
+
         return backgroundRadiusAnim;
     }
 
diff --git a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
index 957db64..882682d 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
@@ -94,6 +94,18 @@
         }
     }
 
+    /**
+     * Cleans up after this controller so it can be garbage collected without leaving traces.
+     */
+    public void dispose() {
+        removeSecondaryListeners();
+
+        if (mLauncher.getRootView() != null && mOnAttachListener != null) {
+            mLauncher.getRootView().removeOnAttachStateChangeListener(mOnAttachListener);
+            mOnAttachListener = null;
+        }
+    }
+
     private void removeSecondaryListeners() {
         if (mCrossWindowBlurListener != null) {
             CrossWindowBlurListeners.getInstance().removeListener(mCrossWindowBlurListener);
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 445a1bd..09376d7 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -16,7 +16,6 @@
 package com.android.launcher3.taskbar;
 
 import static com.android.launcher3.QuickstepTransitionManager.TRANSIENT_TASKBAR_TRANSITION_DURATION;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_HOME_TRANSITION_LISTENER;
 import static com.android.launcher3.statemanager.BaseState.FLAG_NON_INTERACTIVE;
 import static com.android.launcher3.taskbar.TaskbarEduTooltipControllerKt.TOOLTIP_STEP_FEATURES;
 import static com.android.launcher3.taskbar.TaskbarLauncherStateController.FLAG_VISIBLE;
@@ -39,6 +38,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatedFloat;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.logging.InstanceId;
 import com.android.launcher3.logging.InstanceIdSequence;
 import com.android.launcher3.model.data.ItemInfo;
@@ -101,7 +101,7 @@
 
         mLauncher.setTaskbarUIController(this);
 
-        if (!ENABLE_HOME_TRANSITION_LISTENER.get()) {
+        if (!FeatureFlags.enableHomeTransitionListener()) {
             onLauncherVisibilityChanged(mLauncher.hasBeenResumed(), true /* fromInit */);
         }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 6aadd65..6ee151b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -129,6 +129,7 @@
 import java.io.PrintWriter;
 import java.util.Collections;
 import java.util.Optional;
+import java.util.function.Consumer;
 
 /**
  * The {@link ActivityContext} with which we inflate Taskbar-related Views. This allows UI elements
@@ -299,38 +300,31 @@
      * the icon size
      */
     private void applyDeviceProfile(DeviceProfile originDeviceProfile) {
-        mDeviceProfile = originDeviceProfile.toBuilder(this)
-                .withDimensionsOverride(deviceProfile -> {
-                    // Taskbar should match the number of icons of hotseat
-                    deviceProfile.numShownHotseatIcons = originDeviceProfile.numShownHotseatIcons;
-                    // Same QSB width to have a smooth animation
-                    deviceProfile.hotseatQsbWidth = originDeviceProfile.hotseatQsbWidth;
+        Consumer<DeviceProfile> overrideProvider = deviceProfile -> {
+            // Taskbar should match the number of icons of hotseat
+            deviceProfile.numShownHotseatIcons = originDeviceProfile.numShownHotseatIcons;
+            // Same QSB width to have a smooth animation
+            deviceProfile.hotseatQsbWidth = originDeviceProfile.hotseatQsbWidth;
 
-                    // Update icon size
-                    deviceProfile.iconSizePx = deviceProfile.taskbarIconSize;
-                    deviceProfile.updateIconSize(1f, getResources());
-                }).build();
+            // Update icon size
+            deviceProfile.iconSizePx = deviceProfile.taskbarIconSize;
+            deviceProfile.updateIconSize(1f, getResources());
+        };
+        mDeviceProfile = originDeviceProfile.toBuilder(this)
+                .withDimensionsOverride(overrideProvider).build();
 
         if (DisplayController.isTransientTaskbar(this)) {
             mTransientTaskbarDeviceProfile = mDeviceProfile;
             mPersistentTaskbarDeviceProfile = mDeviceProfile
                     .toBuilder(this)
-                    .withDimensionsOverride(deviceProfile -> {
-                        // Update icon size
-                        deviceProfile.iconSizePx = deviceProfile.taskbarIconSize;
-                        deviceProfile.updateIconSize(1f, getResources());
-                    })
+                    .withDimensionsOverride(overrideProvider)
                     .setIsTransientTaskbar(false)
                     .build();
         } else {
             mPersistentTaskbarDeviceProfile = mDeviceProfile;
             mTransientTaskbarDeviceProfile = mDeviceProfile
                     .toBuilder(this)
-                    .withDimensionsOverride(deviceProfile -> {
-                        // Update icon size
-                        deviceProfile.iconSizePx = deviceProfile.taskbarIconSize;
-                        deviceProfile.updateIconSize(1f, getResources());
-                    })
+                    .withDimensionsOverride(overrideProvider)
                     .setIsTransientTaskbar(true)
                     .build();
         }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
index e4ebf65..d6016f1 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
@@ -32,6 +32,7 @@
 import com.android.launcher3.taskbar.TaskbarPinningController.Companion.PINNING_PERSISTENT
 import com.android.launcher3.taskbar.TaskbarPinningController.Companion.PINNING_TRANSIENT
 import com.android.launcher3.util.DisplayController
+import kotlin.math.min
 
 /** Helps draw the taskbar background, made up of a rectangle plus two inverted rounded corners. */
 class TaskbarBackgroundRenderer(private val context: TaskbarActivityContext) {
@@ -153,9 +154,10 @@
             // Draw the background behind taskbar content.
             canvas.drawRect(0f, 0f, canvas.width.toFloat(), persistentTaskbarHeight, paint)
         } else {
-            canvas.translate(0f, canvas.height - maxPersistentTaskbarHeight)
+            val persistentTaskbarHeight = min(maxPersistentTaskbarHeight, backgroundHeight)
+            canvas.translate(0f, canvas.height - persistentTaskbarHeight)
             // Draw the background behind taskbar content.
-            canvas.drawRect(0f, 0f, canvas.width.toFloat(), maxPersistentTaskbarHeight, paint)
+            canvas.drawRect(0f, 0f, canvas.width.toFloat(), persistentTaskbarHeight, paint)
         }
 
         // Draw the inverted rounded corners above the taskbar.
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index d4f42d8..267b15c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -206,7 +206,9 @@
                     updateStateForFlag(FLAG_LAUNCHER_IN_STATE_TRANSITION, false);
                     // TODO(b/279514548) Cleans up bad state that can occur when user interacts with
                     // taskbar on top of transparent activity.
-                    if (finalState == LauncherState.NORMAL && mLauncher.hasBeenResumed()) {
+                    if (!FeatureFlags.enableHomeTransitionListener()
+                            && finalState == LauncherState.NORMAL
+                            && mLauncher.hasBeenResumed()) {
                         updateStateForFlag(FLAG_VISIBLE, true);
                     }
                     applyState();
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index b500c3e..9bb7e67 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -21,6 +21,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
 
+import static com.android.launcher3.BaseActivity.EVENT_DESTROYED;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.config.FeatureFlags.enableTaskbarNoRecreate;
 import static com.android.launcher3.util.DisplayController.TASKBAR_NOT_DESTROYED_TAG;
@@ -30,7 +31,6 @@
 import static com.android.quickstep.util.SystemActionConstants.SYSTEM_ACTION_ID_TASKBAR;
 
 import android.annotation.SuppressLint;
-import android.app.Activity;
 import android.app.PendingIntent;
 import android.content.ComponentCallbacks;
 import android.content.Context;
@@ -61,7 +61,6 @@
 import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.taskbar.unfold.NonDestroyableScopedUnfoldTransitionProgressProvider;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
-import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.SettingsCache;
 import com.android.launcher3.util.SimpleBroadcastReceiver;
@@ -145,27 +144,25 @@
     private final SimpleBroadcastReceiver mTaskbarBroadcastReceiver =
             new SimpleBroadcastReceiver(this::showTaskbarFromBroadcast);
 
-    private final ActivityLifecycleCallbacksAdapter mLifecycleCallbacks =
-            new ActivityLifecycleCallbacksAdapter() {
-                @Override
-                public void onActivityDestroyed(Activity activity) {
-                    if (mActivity != activity) return;
-                    if (mActivity != null) {
-                        mActivity.removeOnDeviceProfileChangeListener(
-                                mDebugActivityDeviceProfileChanged);
-                        Log.d(TASKBAR_NOT_DESTROYED_TAG,
-                                "unregistering activity lifecycle callbacks from "
-                                        + "onActivityDestroyed.");
-                        mActivity.unregisterActivityLifecycleCallbacks(this);
-                    }
-                    mActivity = null;
-                    debugWhyTaskbarNotDestroyed("clearActivity");
-                    if (mTaskbarActivityContext != null) {
-                        mTaskbarActivityContext.setUIController(TaskbarUIController.DEFAULT);
-                    }
-                    mUnfoldProgressProvider.setSourceProvider(null);
-                }
-            };
+    private final Runnable mActivityOnDestroyCallback = new Runnable() {
+        @Override
+        public void run() {
+            if (mActivity != null) {
+                mActivity.removeOnDeviceProfileChangeListener(
+                        mDebugActivityDeviceProfileChanged);
+                Log.d(TASKBAR_NOT_DESTROYED_TAG,
+                        "unregistering activity lifecycle callbacks from "
+                                + "onActivityDestroyed.");
+                mActivity.removeEventCallback(EVENT_DESTROYED, this);
+            }
+            mActivity = null;
+            debugWhyTaskbarNotDestroyed("clearActivity");
+            if (mTaskbarActivityContext != null) {
+                mTaskbarActivityContext.setUIController(TaskbarUIController.DEFAULT);
+            }
+            mUnfoldProgressProvider.setSourceProvider(null);
+        }
+    };
 
     UnfoldTransitionProgressProvider.TransitionProgressListener mUnfoldTransitionProgressListener =
             new UnfoldTransitionProgressProvider.TransitionProgressListener() {
@@ -371,7 +368,7 @@
         mActivity.addOnDeviceProfileChangeListener(mDebugActivityDeviceProfileChanged);
         Log.d(TASKBAR_NOT_DESTROYED_TAG,
                 "registering activity lifecycle callbacks from setActivity().");
-        mActivity.registerActivityLifecycleCallbacks(mLifecycleCallbacks);
+        mActivity.addEventCallback(EVENT_DESTROYED, mActivityOnDestroyCallback);
         UnfoldTransitionProgressProvider unfoldTransitionProgressProvider =
                 getUnfoldTransitionProgressProviderForActivity(activity);
         if (unfoldTransitionProgressProvider != null) {
@@ -547,7 +544,7 @@
             Log.d(TASKBAR_NOT_DESTROYED_TAG,
                     "unregistering activity lifecycle callbacks from "
                             + "removeActivityCallbackAndListeners().");
-            mActivity.unregisterActivityLifecycleCallbacks(mLifecycleCallbacks);
+            mActivity.removeEventCallback(EVENT_DESTROYED, mActivityOnDestroyCallback);
             UnfoldTransitionProgressProvider unfoldTransitionProgressProvider =
                     getUnfoldTransitionProgressProviderForActivity(mActivity);
             if (unfoldTransitionProgressProvider != null) {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index a101c20..22ab79d 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -33,6 +33,7 @@
 import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
 import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_HOME_TRANSITION_LISTENER;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP;
 import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
 import static com.android.launcher3.popup.QuickstepSystemShortcut.getSplitSelectShortcutByPosition;
@@ -263,7 +264,7 @@
         mAppTransitionManager.registerRemoteAnimations();
         mAppTransitionManager.registerRemoteTransitions();
 
-        if (ENABLE_HOME_TRANSITION_LISTENER.get()) {
+        if (FeatureFlags.enableHomeTransitionListener()) {
             mHomeTransitionController = new HomeTransitionController(this);
             mHomeTransitionController.registerHomeTransitionListener();
         }
@@ -375,7 +376,7 @@
         }
 
         if ((changeBits & ACTIVITY_STATE_RESUMED) != 0) {
-            if (!ENABLE_HOME_TRANSITION_LISTENER.get() && mTaskbarUIController != null) {
+            if (!FeatureFlags.enableHomeTransitionListener() && mTaskbarUIController != null) {
                 mTaskbarUIController.onLauncherVisibilityChanged(hasBeenResumed());
             }
         }
@@ -1182,11 +1183,6 @@
     }
 
     @Override
-    public void tryClearAccessibilityFocus(View view) {
-        view.clearAccessibilityFocus();
-    }
-
-    @Override
     protected void onSaveInstanceState(Bundle outState) {
         super.onSaveInstanceState(outState);
 
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index ccd83d2..b06a978 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -23,6 +23,8 @@
 import static com.android.app.animation.Interpolators.ACCELERATE_DECELERATE;
 import static com.android.app.animation.Interpolators.DECELERATE;
 import static com.android.app.animation.Interpolators.OVERSHOOT_1_2;
+import static com.android.launcher3.BaseActivity.EVENT_DESTROYED;
+import static com.android.launcher3.BaseActivity.EVENT_STARTED;
 import static com.android.launcher3.BaseActivity.INVISIBLE_BY_STATE_HANDLER;
 import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
 import static com.android.launcher3.LauncherPrefs.ALL_APPS_OVERVIEW_THRESHOLD;
@@ -62,7 +64,6 @@
 import android.animation.AnimatorSet;
 import android.animation.ValueAnimator;
 import android.annotation.TargetApi;
-import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.TaskInfo;
 import android.app.WindowConfiguration;
@@ -89,6 +90,7 @@
 import android.widget.Toast;
 import android.window.PictureInPictureSurfaceTransaction;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
 
@@ -109,7 +111,6 @@
 import com.android.launcher3.taskbar.TaskbarThresholdUtils;
 import com.android.launcher3.taskbar.TaskbarUIController;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
-import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.SafeCloseable;
 import com.android.launcher3.util.TraceHelper;
@@ -183,19 +184,13 @@
     protected MultiStateCallback mStateCallback;
     protected boolean mCanceled;
     private boolean mRecentsViewScrollLinked = false;
-    private final ActivityLifecycleCallbacksAdapter mLifecycleCallbacks =
-            new ActivityLifecycleCallbacksAdapter() {
-                @Override
-                public void onActivityDestroyed(Activity activity) {
-                    if (mActivity != activity) {
-                        return;
-                    }
-                    ActiveGestureLog.INSTANCE.addLog("Launcher destroyed", LAUNCHER_DESTROYED);
-                    mRecentsView = null;
-                    mActivity = null;
-                    mStateCallback.clearState(STATE_LAUNCHER_PRESENT);
-                }
-            };
+
+    private final Runnable mLauncherOnDestroyCallback = () -> {
+        ActiveGestureLog.INSTANCE.addLog("Launcher destroyed", LAUNCHER_DESTROYED);
+        mRecentsView = null;
+        mActivity = null;
+        mStateCallback.clearState(STATE_LAUNCHER_PRESENT);
+    };
 
     private static int FLAG_COUNT = 0;
     private static int getNextStateFlag(String name) {
@@ -316,8 +311,9 @@
     private final int mSplashMainWindowShiftLength;
 
     private final Runnable mOnDeferredActivityLaunch = this::onDeferredActivityLaunch;
+    private final Runnable mLauncherOnStartCallback = this::onLauncherStart;
 
-    private SwipePipToHomeAnimator mSwipePipToHomeAnimator;
+    @Nullable private SwipePipToHomeAnimator mSwipePipToHomeAnimator;
     protected boolean mIsSwipingPipToHome;
     // TODO(b/195473090) no split PIP for now, remove once we have more clarity
     //  can try to have RectFSpringAnim evaluate multiple rects at once
@@ -489,6 +485,7 @@
                 mGestureState.setState(STATE_RECENTS_SCROLLING_FINISHED);
                 return true;
             }
+            resetLauncherListeners();
 
             // The launcher may have been recreated as a result of device rotation.
             int oldState = mStateCallback.getState() & ~LAUNCHER_UI_STATES;
@@ -512,7 +509,7 @@
         if (alreadyOnHome) {
             onLauncherStart();
         } else {
-            activity.runOnceOnStart(this::onLauncherStart);
+            activity.addEventCallback(EVENT_STARTED, mLauncherOnStartCallback);
         }
 
         // Set up a entire animation lifecycle callback to notify the current recents view when
@@ -537,9 +534,8 @@
 
         setupRecentsViewUi();
         mRecentsView.runOnPageScrollsInitialized(this::linkRecentsViewScroll);
-        activity.runOnBindToTouchInteractionService(this::onLauncherBindToService);
-
-        mActivity.registerActivityLifecycleCallbacks(mLifecycleCallbacks);
+        mActivity.runOnBindToTouchInteractionService(this::onLauncherBindToService);
+        mActivity.addEventCallback(EVENT_DESTROYED, mLauncherOnDestroyCallback);
         return true;
     }
 
@@ -676,6 +672,9 @@
         if (Arrays.stream(runningTasks).anyMatch(Objects::isNull)) {
             return;
         }
+        if (mRecentsView == null) {
+            return;
+        }
         mRecentsView.onGestureAnimationStart(runningTasks, mDeviceState.getRotationTouchHelper());
     }
 
@@ -1263,8 +1262,8 @@
             return isSwipeUp ? ALL_APPS : LAST_TASK;
         }
         if (!isSwipeUp) {
-            final boolean isCenteredOnNewTask =
-                    mRecentsView.getDestinationPage() != mRecentsView.getRunningTaskIndex();
+            final boolean isCenteredOnNewTask = mRecentsView != null
+                    && mRecentsView.getDestinationPage() != mRecentsView.getRunningTaskIndex();
             return willGoToNewTask || isCenteredOnNewTask ? NEW_TASK : LAST_TASK;
         }
 
@@ -1540,11 +1539,13 @@
             HomeAnimationFactory homeAnimFactory =
                     createHomeAnimationFactory(cookies, duration, isTranslucent, appCanEnterPip,
                             runningTaskTarget);
-            mIsSwipingPipToHome = !mIsSwipeForSplit && appCanEnterPip;
+            SwipePipToHomeAnimator swipePipToHomeAnimator = !mIsSwipeForSplit && appCanEnterPip
+                    ? createWindowAnimationToPip(homeAnimFactory, runningTaskTarget, start)
+                    : null;
+            mIsSwipingPipToHome = swipePipToHomeAnimator != null;
             final RectFSpringAnim[] windowAnim;
             if (mIsSwipingPipToHome) {
-                mSwipePipToHomeAnimator = createWindowAnimationToPip(
-                        homeAnimFactory, runningTaskTarget, start);
+                mSwipePipToHomeAnimator = swipePipToHomeAnimator;
                 mSwipePipToHomeAnimators[0] = mSwipePipToHomeAnimator;
                 if (mSwipePipToHomeReleaseCheck != null) {
                     mSwipePipToHomeReleaseCheck.setCanRelease(false);
@@ -1552,6 +1553,9 @@
 
                 // grab a screenshot before the PipContentOverlay gets parented on top of the task
                 UI_HELPER_EXECUTOR.execute(() -> {
+                    if (mRecentsAnimationController == null) {
+                        return;
+                    }
                     // Directly use top task, split to pip handled on shell side
                     final int taskId = mGestureState.getTopRunningTaskId();
                     mTaskSnapshotCache.put(taskId,
@@ -1669,6 +1673,10 @@
     @Nullable
     private SwipePipToHomeAnimator createWindowAnimationToPip(HomeAnimationFactory homeAnimFactory,
             RemoteAnimationTarget runningTaskTarget, float startProgress) {
+        if (mRecentsView == null) {
+            // Overview was destroyed, bail early.
+            return null;
+        }
         // Directly animate the app to PiP (picture-in-picture) mode
         final ActivityManager.RunningTaskInfo taskInfo = runningTaskTarget.taskInfo;
         final RecentsOrientedState orientationState = mRemoteTargetHandles[0].getTaskViewSimulator()
@@ -1809,7 +1817,8 @@
     private void continueComputingRecentsScrollIfNecessary() {
         if (!mGestureState.hasState(STATE_RECENTS_SCROLLING_FINISHED)
                 && !mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)
-                && !mCanceled) {
+                && !mCanceled
+                && mRecentsView != null) {
             computeRecentsScrollIfInvisible();
             mRecentsView.postOnAnimation(this::continueComputingRecentsScrollIfNecessary);
         }
@@ -1853,7 +1862,6 @@
         if (mActivity != null) {
             // In the off chance that the gesture ends before Launcher is started, we should clear
             // the callback here so that it doesn't update with the wrong state
-            mActivity.clearRunOnceOnStartCallback();
             resetLauncherListeners();
         }
         if (mGestureState.isRecentsAnimationRunning() && mGestureState.getEndTarget() != null
@@ -1919,7 +1927,7 @@
     private void reset() {
         mStateCallback.setStateOnUiThread(STATE_HANDLER_INVALIDATED);
         if (mActivity != null) {
-            mActivity.unregisterActivityLifecycleCallbacks(mLifecycleCallbacks);
+            mActivity.removeEventCallback(EVENT_DESTROYED, mLauncherOnDestroyCallback);
         }
     }
 
@@ -1990,6 +1998,9 @@
      * continued quick switch gesture, which cancels the previous handler but doesn't invalidate it.
      */
     private void resetLauncherListeners() {
+        mActivity.removeEventCallback(EVENT_STARTED, mLauncherOnStartCallback);
+        mActivity.removeEventCallback(EVENT_DESTROYED, mLauncherOnDestroyCallback);
+
         mActivity.getRootView().setOnApplyWindowInsetsListener(null);
 
         if (mRecentsView != null) {
@@ -2111,7 +2122,7 @@
      * from Launcher to WM.
      */
     private void maybeAbortSwipePipToHome() {
-        if (mIsSwipingPipToHome && mSwipePipToHomeAnimators[0] != null) {
+        if (mIsSwipingPipToHome && mSwipePipToHomeAnimator != null) {
             SystemUiProxy.INSTANCE.get(mContext).abortSwipePipToHome(
                     mSwipePipToHomeAnimator.getTaskId(),
                     mSwipePipToHomeAnimator.getComponentName());
@@ -2125,7 +2136,10 @@
      * This should happen before {@link #finishRecentsControllerToHome(Runnable)}.
      */
     private void maybeFinishSwipePipToHome() {
-        if (mIsSwipingPipToHome && mSwipePipToHomeAnimators[0] != null) {
+        if (mRecentsAnimationController == null) {
+            return;
+        }
+        if (mIsSwipingPipToHome && mSwipePipToHomeAnimator != null) {
             mRecentsAnimationController.setFinishTaskTransaction(
                     mSwipePipToHomeAnimator.getTaskId(),
                     mSwipePipToHomeAnimator.getFinishTransaction(),
@@ -2149,7 +2163,7 @@
     protected abstract void finishRecentsControllerToHome(Runnable callback);
 
     private void setupLauncherUiAfterSwipeUpToRecentsAnimation() {
-        if (mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)) {
+        if (mStateCallback.hasStates(STATE_HANDLER_INVALIDATED) || mRecentsView == null) {
             return;
         }
         endLauncherTransitionController();
@@ -2183,6 +2197,9 @@
     }
 
     protected void linkRecentsViewScroll() {
+        if (mRecentsView == null) {
+            return;
+        }
         SurfaceTransactionApplier applier = new SurfaceTransactionApplier(mRecentsView);
         runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.getTransformParams()
                         .setSyncTransactionApplier(applier));
@@ -2190,9 +2207,13 @@
                 mRecentsAnimationTargets.addReleaseCheck(applier));
 
         mRecentsView.addOnScrollChangedListener(mOnRecentsScrollListener);
-        runOnRecentsAnimationAndLauncherBound(() ->
-                mRecentsView.setRecentsAnimationTargets(mRecentsAnimationController,
-                        mRecentsAnimationTargets));
+        runOnRecentsAnimationAndLauncherBound(() -> {
+            if (mRecentsView == null) {
+                return;
+            }
+            mRecentsView.setRecentsAnimationTargets(
+                    mRecentsAnimationController, mRecentsAnimationTargets);
+        });
 
         // Disable scrolling in RecentsView for trackpad 3-finger swipe up gesture.
         if (!mGestureState.isThreeFingerTrackpadGesture()) {
@@ -2299,7 +2320,7 @@
     }
 
     @Override
-    public void onTasksAppeared(RemoteAnimationTarget[] appearedTaskTargets) {
+    public void onTasksAppeared(@NonNull RemoteAnimationTarget[] appearedTaskTargets) {
         if (mRecentsAnimationController != null) {
             boolean hasStartedTaskBefore = Arrays.stream(appearedTaskTargets).anyMatch(
                     mGestureState.mLastStartedTaskIdPredicate);
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 468d96a..038a022 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -17,7 +17,6 @@
 
 import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE;
 
-import static com.android.launcher3.config.FeatureFlags.ENABLE_HOME_TRANSITION_LISTENER;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
@@ -63,6 +62,7 @@
 import com.android.internal.logging.InstanceId;
 import com.android.internal.util.ScreenshotRequest;
 import com.android.internal.view.AppearanceRegion;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.Preconditions;
 import com.android.quickstep.util.ActiveGestureLog;
@@ -1048,7 +1048,7 @@
     }
 
     public void setHomeTransitionListener(IHomeTransitionListener listener) {
-        if (!ENABLE_HOME_TRANSITION_LISTENER.get()) {
+        if (!FeatureFlags.enableHomeTransitionListener()) {
             return;
         }
 
diff --git a/quickstep/src/com/android/quickstep/util/SurfaceTransactionApplier.java b/quickstep/src/com/android/quickstep/util/SurfaceTransactionApplier.java
index bb028a7..df5765c 100644
--- a/quickstep/src/com/android/quickstep/util/SurfaceTransactionApplier.java
+++ b/quickstep/src/com/android/quickstep/util/SurfaceTransactionApplier.java
@@ -25,6 +25,8 @@
 import android.view.View.OnAttachStateChangeListener;
 import android.view.ViewRootImpl;
 
+import androidx.annotation.NonNull;
+
 import com.android.quickstep.RemoteAnimationTargets.ReleaseCheck;
 
 /**
@@ -48,7 +50,7 @@
     /**
      * @param targetView The view in the surface that acts as synchronization anchor.
      */
-    public SurfaceTransactionApplier(View targetView) {
+    public SurfaceTransactionApplier(@NonNull View targetView) {
         if (targetView.isAttachedToWindow()) {
             initialize(targetView);
         } else {
diff --git a/quickstep/src/com/android/quickstep/util/TaskRemovedDuringLaunchListener.java b/quickstep/src/com/android/quickstep/util/TaskRemovedDuringLaunchListener.java
index d7b3431..cdadd71 100644
--- a/quickstep/src/com/android/quickstep/util/TaskRemovedDuringLaunchListener.java
+++ b/quickstep/src/com/android/quickstep/util/TaskRemovedDuringLaunchListener.java
@@ -18,11 +18,13 @@
 
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 
-import android.app.Activity;
+import static com.android.launcher3.BaseActivity.EVENT_DESTROYED;
+import static com.android.launcher3.BaseActivity.EVENT_RESUMED;
+import static com.android.launcher3.BaseActivity.EVENT_STOPPED;
 
 import androidx.annotation.NonNull;
 
-import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter;
+import com.android.launcher3.BaseActivity;
 import com.android.quickstep.RecentsModel;
 
 /**
@@ -34,19 +36,28 @@
  * If we hit either of those signals and the task is no longer valid, then the registered failure
  * callback will be notified.
  */
-public class TaskRemovedDuringLaunchListener implements ActivityLifecycleCallbacksAdapter {
+public class TaskRemovedDuringLaunchListener {
 
-    private Activity mActivity;
+    private BaseActivity mActivity;
     private int mLaunchedTaskId = INVALID_TASK_ID;
     private Runnable mTaskLaunchFailedCallback = null;
 
+    private final Runnable mUnregisterCallback = this::unregister;
+    private final Runnable mResumeCallback = this::checkTaskLaunchFailed;
+
     /**
      * Registers a failure listener callback if it detects a scenario in which an app launch
      * failed before the transition finished.
      */
-    public void register(Activity activity, int launchedTaskId,
+    public void register(BaseActivity activity, int launchedTaskId,
             @NonNull Runnable taskLaunchFailedCallback) {
-        activity.registerActivityLifecycleCallbacks(this);
+        // The normal task launch case, Launcher stops and updates its state correctly
+        activity.addEventCallback(EVENT_STOPPED, mUnregisterCallback);
+        // The transition hasn't finished but Launcher was resumed, check if the launch failed
+        activity.addEventCallback(EVENT_RESUMED, mResumeCallback);
+        // If we somehow don't get any of the above signals, then just unregister this listener
+        activity.addEventCallback(EVENT_DESTROYED, mUnregisterCallback);
+
         mActivity = activity;
         mLaunchedTaskId = launchedTaskId;
         mTaskLaunchFailedCallback = taskLaunchFailedCallback;
@@ -56,7 +67,10 @@
      * Unregisters the failure listener.
      */
     private void unregister() {
-        mActivity.unregisterActivityLifecycleCallbacks(this);
+        mActivity.removeEventCallback(EVENT_STOPPED, mUnregisterCallback);
+        mActivity.removeEventCallback(EVENT_RESUMED, mResumeCallback);
+        mActivity.removeEventCallback(EVENT_DESTROYED, mUnregisterCallback);
+
         mActivity = null;
         mLaunchedTaskId = INVALID_TASK_ID;
         mTaskLaunchFailedCallback = null;
@@ -70,24 +84,6 @@
         checkTaskLaunchFailed();
     }
 
-    @Override
-    public void onActivityStopped(Activity activity) {
-        // The normal task launch case, Launcher stops and updates its state correctly
-        unregister();
-    }
-
-    @Override
-    public void onActivityResumed(Activity activity) {
-        // The transition hasn't finished but Launcher was resumed, check if the launch failed
-        checkTaskLaunchFailed();
-    }
-
-    @Override
-    public void onActivityDestroyed(Activity activity) {
-        // If we somehow don't get any of the above signals, then just unregister this listener
-        unregister();
-    }
-
     private void checkTaskLaunchFailed() {
         if (mLaunchedTaskId != INVALID_TASK_ID) {
             final int launchedTaskId = mLaunchedTaskId;
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index 767aa15..5d8e53e 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -17,6 +17,7 @@
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 
+import static com.android.launcher3.Flags.enableGridOnlyOverview;
 import static com.android.launcher3.states.RotationHelper.deltaRotation;
 import static com.android.launcher3.touch.PagedOrientationHandler.MATRIX_POST_TRANSLATE;
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
@@ -108,6 +109,7 @@
     private boolean mIsDesktopTask;
     private int mTaskRectTranslationX;
     private int mTaskRectTranslationY;
+    private int mPivotOffsetX;
 
     public TaskViewSimulator(Context context, BaseActivityInterface sizeStrategy) {
         mContext = context;
@@ -179,9 +181,10 @@
             // Ensure the task rect is inside the full task rect
             mTaskRect.offset(fullTaskSize.left, fullTaskSize.top);
         } else {
-            fullTaskSize = mTaskRect;
+            fullTaskSize = new Rect(mTaskRect);
+            mTaskRect.offset(mTaskRectTranslationX, mTaskRectTranslationY);
         }
-        fullTaskSize.offset(mTaskRectTranslationX, mTaskRectTranslationY);
+        fullTaskSize.offset(mTaskRectTranslationX + mPivotOffsetX, mTaskRectTranslationY);
         return mOrientationState.getFullScreenScaleAndPivot(fullTaskSize, mDp, mPivot);
     }
 
@@ -265,6 +268,11 @@
      */
     public void addAppToOverviewAnim(PendingAnimation pa, TimeInterpolator interpolator) {
         pa.addFloat(fullScreenProgress, AnimatedFloat.VALUE, 1, 0, interpolator);
+        if (enableGridOnlyOverview() && mDp.isTablet) {
+            int translationXToMiddle = mDp.widthPx / 2 - mTaskRect.centerX();
+            taskPrimaryTranslation.value = translationXToMiddle;
+            mPivotOffsetX = translationXToMiddle;
+        }
         pa.addFloat(recentsViewScale, AnimatedFloat.VALUE, getFullScreenScale(), 1, interpolator);
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index c44d1f2..7972999 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -2101,11 +2101,15 @@
         }
 
         float accumulatedTranslationX = 0;
+        float translateXToMiddle = enableGridOnlyOverview() && mActivity.getDeviceProfile().isTablet
+                ? mActivity.getDeviceProfile().widthPx / 2 - mLastComputedGridTaskSize.centerX()
+                : 0;
         for (int i = 0; i < taskCount; i++) {
             TaskView taskView = requireTaskViewAt(i);
             taskView.updateTaskSize();
             taskView.getPrimaryNonGridTranslationProperty().set(taskView, accumulatedTranslationX);
             taskView.getSecondaryNonGridTranslationProperty().set(taskView, 0f);
+            taskView.setNonGridPivotTranslationX(translateXToMiddle);
             // Compensate space caused by TaskView scaling.
             float widthDiff =
                     taskView.getLayoutParams().width * (1 - taskView.getNonGridScale());
@@ -4322,14 +4326,19 @@
 
             Utilities.getPivotsForScalingRectToRect(mTempRect, selectedTaskPosition,
                     mTempPointF);
-            setPivotX(mTempPointF.x);
-            setPivotY(mTempPointF.y);
         } else {
+            mTempRect.set(mLastComputedTaskSize);
+            // Only update pivot when it is tablet and not in grid yet, so the pivot is correct
+            // for non-current tasks when swiping up to overview
+            if (enableGridOnlyOverview() && mActivity.getDeviceProfile().isTablet
+                    && !mOverviewGridEnabled) {
+                mTempRect.offset(mActivity.getDeviceProfile().widthPx / 2 - mTempRect.centerX(), 0);
+            }
             getPagedViewOrientedState().getFullScreenScaleAndPivot(mTempRect,
                     mActivity.getDeviceProfile(), mTempPointF);
-            setPivotX(mTempPointF.x);
-            setPivotY(mTempPointF.y);
         }
+        setPivotX(mTempPointF.x);
+        setPivotY(mTempPointF.y);
     }
 
     private void updatePageOffsets() {
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 2b3cc5b..5093f22 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -385,6 +385,7 @@
     // switch.
     private float mNonGridTranslationX;
     private float mNonGridTranslationY;
+    private float mNonGridPivotTranslationX;
     // Used when in SplitScreenSelectState
     private float mSplitSelectTranslationY;
     private float mSplitSelectTranslationX;
@@ -1285,8 +1286,8 @@
     }
 
     protected void resetPersistentViewTransforms() {
-        mNonGridTranslationX = mNonGridTranslationY =
-                mGridTranslationX = mGridTranslationY = mBoxTranslationY = 0f;
+        mNonGridTranslationX = mNonGridTranslationY = mGridTranslationX =
+                mGridTranslationY = mBoxTranslationY = mNonGridPivotTranslationX = 0f;
         resetViewTransforms();
     }
 
@@ -1488,6 +1489,14 @@
         applyTranslationX();
     }
 
+    /**
+     * Set translation X for non-grid pivot
+     */
+    public void setNonGridPivotTranslationX(float nonGridPivotTranslationX) {
+        mNonGridPivotTranslationX = nonGridPivotTranslationX;
+        applyTranslationX();
+    }
+
     public float getScrollAdjustment(boolean gridEnabled) {
         float scrollAdjustment = 0;
         if (gridEnabled) {
@@ -1530,7 +1539,8 @@
      * change according to a temporary state (e.g. task offset).
      */
     public float getPersistentTranslationX() {
-        return getNonGridTrans(mNonGridTranslationX) + getGridTrans(mGridTranslationX);
+        return getNonGridTrans(mNonGridTranslationX) + getGridTrans(mGridTranslationX)
+                + getNonGridTrans(mNonGridPivotTranslationX);
     }
 
     /**
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index 05a6452..1049314 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -29,7 +29,6 @@
 import android.content.res.Configuration;
 import android.os.Bundle;
 import android.util.Log;
-import android.view.View;
 import android.window.OnBackInvokedDispatcher;
 
 import androidx.annotation.IntDef;
@@ -38,6 +37,7 @@
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.shared.TestProtocol;
+import com.android.launcher3.util.RunnableList;
 import com.android.launcher3.util.SystemUiController;
 import com.android.launcher3.util.ViewCache;
 import com.android.launcher3.views.ActivityContext;
@@ -153,6 +153,18 @@
 
     private final ViewCache mViewCache = new ViewCache();
 
+    @Retention(SOURCE)
+    @IntDef({EVENT_STARTED, EVENT_RESUMED, EVENT_STOPPED, EVENT_DESTROYED})
+    public @interface ActivityEvent { }
+    public static final int EVENT_STARTED = 0;
+    public static final int EVENT_RESUMED = 1;
+    public static final int EVENT_STOPPED = 2;
+    public static final int EVENT_DESTROYED = 3;
+
+    // Callback array that corresponds to events defined in @ActivityEvent
+    private final RunnableList[] mEventCallbacks =
+            {new RunnableList(), new RunnableList(), new RunnableList(), new RunnableList()};
+
     @Override
     public ViewCache getViewCache() {
         return mViewCache;
@@ -205,12 +217,14 @@
     protected void onStart() {
         addActivityFlags(ACTIVITY_STATE_STARTED);
         super.onStart();
+        mEventCallbacks[EVENT_STARTED].executeAllAndClear();
     }
 
     @Override
     protected void onResume() {
         setResumed();
         super.onResume();
+        mEventCallbacks[EVENT_RESUMED].executeAllAndClear();
     }
 
     @Override
@@ -232,6 +246,8 @@
         removeActivityFlags(ACTIVITY_STATE_STARTED | ACTIVITY_STATE_USER_ACTIVE);
         mForceInvisible = 0;
         super.onStop();
+        mEventCallbacks[EVENT_STOPPED].executeAllAndClear();
+
 
         // Reset the overridden sysui flags used for the task-swipe launch animation, this is a
         // catch all for if we do not get resumed (and therefore not paused below)
@@ -239,6 +255,12 @@
     }
 
     @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        mEventCallbacks[EVENT_DESTROYED].executeAllAndClear();
+    }
+
+    @Override
     protected void onPause() {
         setPaused();
         super.onPause();
@@ -258,7 +280,6 @@
         } else {
             removeActivityFlags(ACTIVITY_STATE_WINDOW_FOCUSED);
         }
-
     }
 
     protected void registerBackDispatcher() {
@@ -364,9 +385,15 @@
     }
 
     /**
-     * Attempts to clear accessibility focus on {@param view}.
+     * Adds a callback for the provided activity event
      */
-    public void tryClearAccessibilityFocus(View view) {
+    public void addEventCallback(@ActivityEvent int event, Runnable callback) {
+        mEventCallbacks[event].add(callback);
+    }
+
+    /** Removes a previously added callback */
+    public void removeEventCallback(@ActivityEvent int event, Runnable callback) {
+        mEventCallbacks[event].remove(callback);
     }
 
     public interface MultiWindowModeChangedListener {
diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java
index 808cf70..f8ed4df 100644
--- a/src/com/android/launcher3/BaseDraggingActivity.java
+++ b/src/com/android/launcher3/BaseDraggingActivity.java
@@ -38,7 +38,6 @@
 import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
 import com.android.launcher3.util.DisplayController.Info;
 import com.android.launcher3.util.OnColorHintListener;
-import com.android.launcher3.util.RunnableList;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.TraceHelper;
 import com.android.launcher3.util.WallpaperColorHints;
@@ -51,8 +50,6 @@
 public abstract class BaseDraggingActivity extends BaseActivity
         implements OnColorHintListener, DisplayInfoChangeListener {
 
-    private static final String TAG = "BaseDraggingActivity";
-
     // When starting an action mode, setting this tag will cause the action mode to be cancelled
     // automatically when user interacts with the launcher.
     public static final Object AUTO_CANCEL_ACTION_MODE = new Object();
@@ -60,8 +57,6 @@
     private ActionMode mCurrentActionMode;
     protected boolean mIsSafeModeEnabled;
 
-    private Runnable mOnStartCallback;
-    private final RunnableList mOnResumeCallbacks = new RunnableList();
     private int mThemeRes = R.style.AppTheme;
 
     @Override
@@ -81,16 +76,6 @@
         }
     }
 
-    @Override
-    protected void onResume() {
-        super.onResume();
-        mOnResumeCallbacks.executeAllAndClear();
-    }
-
-    public void addOnResumeCallback(Runnable callback) {
-        mOnResumeCallbacks.add(callback);
-    }
-
     @MainThread
     @Override
     public void onColorHintsChanged(int colorHints) {
@@ -146,42 +131,24 @@
     @NonNull
     public ActivityOptionsWrapper getActivityLaunchOptions(View v, @Nullable ItemInfo item) {
         ActivityOptionsWrapper wrapper = super.getActivityLaunchOptions(v, item);
-        addOnResumeCallback(wrapper.onEndCallback::executeAllAndDestroy);
+        addEventCallback(EVENT_RESUMED, wrapper.onEndCallback::executeAllAndDestroy);
         return wrapper;
     }
 
     @Override
     public ActivityOptionsWrapper makeDefaultActivityOptions(int splashScreenStyle) {
         ActivityOptionsWrapper wrapper = super.makeDefaultActivityOptions(splashScreenStyle);
-        addOnResumeCallback(wrapper.onEndCallback::executeAllAndDestroy);
+        addEventCallback(EVENT_RESUMED, wrapper.onEndCallback::executeAllAndDestroy);
         return wrapper;
     }
 
     @Override
-    protected void onStart() {
-        super.onStart();
-
-        if (mOnStartCallback != null) {
-            mOnStartCallback.run();
-            mOnStartCallback = null;
-        }
-    }
-
-    @Override
     protected void onDestroy() {
         super.onDestroy();
         DisplayController.INSTANCE.get(this).removeChangeListener(this);
         WallpaperColorHints.get(this).unregisterOnColorsChangedListener(this);
     }
 
-    public void runOnceOnStart(Runnable action) {
-        mOnStartCallback = action;
-    }
-
-    public void clearRunOnceOnStartCallback() {
-        mOnStartCallback = null;
-    }
-
     protected void onDeviceProfileInitiated() {
         if (mDeviceProfile.isVerticalBarLayout()) {
             mDeviceProfile.updateIsSeascape(this);
diff --git a/src/com/android/launcher3/DropTargetHandler.kt b/src/com/android/launcher3/DropTargetHandler.kt
index 6560e16..78f2862 100644
--- a/src/com/android/launcher3/DropTargetHandler.kt
+++ b/src/com/android/launcher3/DropTargetHandler.kt
@@ -2,6 +2,7 @@
 
 import android.content.ComponentName
 import android.view.View
+import com.android.launcher3.BaseDraggingActivity.EVENT_RESUMED
 import com.android.launcher3.DropTarget.DragObject
 import com.android.launcher3.SecondaryDropTarget.DeferredOnComplete
 import com.android.launcher3.dragndrop.DragLayer
@@ -32,7 +33,7 @@
                 if (d.dragSource is SecondaryDropTarget.DeferredOnComplete) {
                     target?.let {
                         deferred.mPackageName = it.packageName
-                        mLauncher.addOnResumeCallback { deferred.onLauncherResume() }
+                        mLauncher.addEventCallback(EVENT_RESUMED) { deferred.onLauncherResume() }
                     }
                         ?: deferred.sendFailure()
                 }
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 1f64cd8..edff48b 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -690,7 +690,7 @@
 
     private void switchOverlay(Supplier<LauncherOverlayManager> overlaySupplier) {
         if (mOverlayManager != null) {
-            mOverlayManager.onActivityDestroyed(this);
+            mOverlayManager.onActivityDestroyed();
         }
         mOverlayManager = overlaySupplier.get();
         if (getRootView().isAttachedToWindow()) {
@@ -1031,7 +1031,7 @@
         if (mDeferOverlayCallbacks) {
             checkIfOverlayStillDeferred();
         } else {
-            mOverlayManager.onActivityStopped(this);
+            mOverlayManager.onActivityStopped();
         }
         hideKeyboard();
         logStopAndResume(false /* isResume */);
@@ -1045,7 +1045,7 @@
         TraceHelper.INSTANCE.beginSection(ON_START_EVT);
         super.onStart();
         if (!mDeferOverlayCallbacks) {
-            mOverlayManager.onActivityStarted(this);
+            mOverlayManager.onActivityStarted();
         }
 
         mAppWidgetHolder.setActivityStarted(true);
@@ -1114,15 +1114,15 @@
 
         // Move the client to the correct state. Calling the same method twice is no-op.
         if (isStarted()) {
-            mOverlayManager.onActivityStarted(this);
+            mOverlayManager.onActivityStarted();
         }
         if (hasBeenResumed()) {
-            mOverlayManager.onActivityResumed(this);
+            mOverlayManager.onActivityResumed();
         } else {
-            mOverlayManager.onActivityPaused(this);
+            mOverlayManager.onActivityPaused();
         }
         if (!isStarted()) {
-            mOverlayManager.onActivityStopped(this);
+            mOverlayManager.onActivityStopped();
         }
     }
 
@@ -1222,7 +1222,7 @@
         if (mDeferOverlayCallbacks) {
             scheduleDeferredCheck();
         } else {
-            mOverlayManager.onActivityResumed(this);
+            mOverlayManager.onActivityResumed();
         }
 
         DragView.removeAllViews(this);
@@ -1240,7 +1240,7 @@
         mDropTargetBar.animateToVisibility(false);
 
         if (!mDeferOverlayCallbacks) {
-            mOverlayManager.onActivityPaused(this);
+            mOverlayManager.onActivityPaused();
         }
         mAppWidgetHolder.setActivityResumed(false);
     }
@@ -1685,7 +1685,6 @@
         }
 
         super.onSaveInstanceState(outState);
-        mOverlayManager.onActivitySaveInstanceState(this, outState);
     }
 
     @Override
@@ -1711,7 +1710,7 @@
         clearPendingBinds();
         LauncherAppState.getIDP(this).removeOnChangeListener(this);
 
-        mOverlayManager.onActivityDestroyed(this);
+        mOverlayManager.onActivityDestroyed();
     }
 
     public LauncherAccessibilityDelegate getAccessibilityDelegate() {
@@ -1744,7 +1743,7 @@
         try {
             super.startIntentSenderForResult(intent, requestCode,
                     fillInIntent, flagsMask, flagsValues, extraFlags, options);
-        } catch (IntentSender.SendIntentException e) {
+        } catch (Exception e) {
             throw new ActivityNotFoundException();
         }
     }
@@ -2006,7 +2005,7 @@
             // Workaround an issue where the WM launch animation is clobbered when finishing the
             // recents animation into launcher. Defer launching the activity until Launcher is
             // next resumed.
-            addOnResumeCallback(() -> {
+            addEventCallback(EVENT_RESUMED, () -> {
                 RunnableList actualResult = startActivitySafely(v, intent, item);
                 if (actualResult != null) {
                     actualResult.add(result::executeAllAndDestroy);
diff --git a/src/com/android/launcher3/LauncherPrefs.kt b/src/com/android/launcher3/LauncherPrefs.kt
index c6a9283..4f8caab 100644
--- a/src/com/android/launcher3/LauncherPrefs.kt
+++ b/src/com/android/launcher3/LauncherPrefs.kt
@@ -20,11 +20,12 @@
 import android.content.SharedPreferences
 import android.content.SharedPreferences.OnSharedPreferenceChangeListener
 import android.util.Log
-import android.view.ViewConfiguration
 import androidx.annotation.VisibleForTesting
 import com.android.launcher3.BuildConfig.WIDGET_ON_FIRST_SCREEN
 import com.android.launcher3.LauncherFiles.DEVICE_PREFERENCES_KEY
 import com.android.launcher3.LauncherFiles.SHARED_PREFERENCES_KEY
+import com.android.launcher3.config.FeatureFlags.LPNH_SLOP_PERCENTAGE
+import com.android.launcher3.config.FeatureFlags.LPNH_TIMEOUT_MS
 import com.android.launcher3.model.DeviceGridState
 import com.android.launcher3.pm.InstallSessionHelper
 import com.android.launcher3.provider.RestoreDbTask
@@ -310,16 +311,16 @@
             )
         @JvmField
         val LONG_PRESS_NAV_HANDLE_SLOP_PERCENTAGE =
-                nonRestorableItem(
-                        "pref_long_press_nav_handle_slop_multiplier",
-                        100,
+            nonRestorableItem(
+                "pref_long_press_nav_handle_slop_percentage",
+                        LPNH_SLOP_PERCENTAGE.get(),
                         EncryptionType.MOVE_TO_DEVICE_PROTECTED
-                )
+            )
         @JvmField
         val LONG_PRESS_NAV_HANDLE_TIMEOUT_MS =
                 nonRestorableItem(
                         "pref_long_press_nav_handle_timeout_ms",
-                        ViewConfiguration.getLongPressTimeout(),
+                        LPNH_TIMEOUT_MS.get(),
                         EncryptionType.MOVE_TO_DEVICE_PROTECTED
                 )
         @JvmField
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 4d8166c..4e7ec0b 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -21,8 +21,11 @@
 import static com.android.launcher3.config.FeatureFlags.FlagState.ENABLED;
 import static com.android.launcher3.config.FeatureFlags.FlagState.TEAMFOOD;
 import static com.android.launcher3.uioverrides.flags.FlagsFactory.getDebugFlag;
+import static com.android.launcher3.uioverrides.flags.FlagsFactory.getIntFlag;
 import static com.android.launcher3.uioverrides.flags.FlagsFactory.getReleaseFlag;
 
+import android.view.ViewConfiguration;
+
 import androidx.annotation.VisibleForTesting;
 
 import com.android.launcher3.BuildConfig;
@@ -113,9 +116,17 @@
                     "Allow entering All Apps from Overview (e.g. long swipe up from app)");
 
     public static final BooleanFlag CUSTOM_LPNH_THRESHOLDS =
-            getDebugFlag(301680992, "CUSTOM_LPNH_THRESHOLDS", DISABLED,
+            getReleaseFlag(301680992, "CUSTOM_LPNH_THRESHOLDS", DISABLED,
                     "Add dev options to customize the LPNH trigger slop and milliseconds");
 
+    public static final IntFlag LPNH_SLOP_PERCENTAGE =
+            getIntFlag(301680992, "LPNH_SLOP_PERCENTAGE", 100,
+                    "Controls touch slop percentage for lpnh");
+
+    public static final IntFlag LPNH_TIMEOUT_MS =
+            getIntFlag(301680992, "LPNH_TIMEOUT_MS", ViewConfiguration.getLongPressTimeout(),
+                    "Controls lpnh timeout in milliseconds");
+
     public static final BooleanFlag ENABLE_SHOW_KEYBOARD_OPTION_IN_ALL_APPS = getReleaseFlag(
             270394468, "ENABLE_SHOW_KEYBOARD_OPTION_IN_ALL_APPS", ENABLED,
             "Enable option to show keyboard when going to all-apps");
@@ -305,10 +316,15 @@
             "ENABLE_DYNAMIC_TASKBAR_THRESHOLDS", ENABLED,
             "Enables taskbar thresholds that scale based on screen size.");
 
+    // Aconfig migration complete for ENABLE_HOME_TRANSITION_LISTENER.
     public static final BooleanFlag ENABLE_HOME_TRANSITION_LISTENER = getDebugFlag(306053414,
-            "ENABLE_HOME_TRANSITION_LISTENER", DISABLED,
+            "ENABLE_HOME_TRANSITION_LISTENER", TEAMFOOD,
             "Enables launcher to listen to all transitions that include home activity.");
 
+    public static boolean enableHomeTransitionListener() {
+        return ENABLE_HOME_TRANSITION_LISTENER.get() || Flags.enableHomeTransitionListener();
+    }
+
     // TODO(Block 21): Clean up flags
     public static final BooleanFlag ENABLE_APP_ICON_FOR_INLINE_SHORTCUTS = getDebugFlag(270395087,
             "ENABLE_APP_ICON_IN_INLINE_SHORTCUTS", DISABLED, "Show app icon for inline shortcut");
diff --git a/src/com/android/launcher3/model/DatabaseHelper.java b/src/com/android/launcher3/model/DatabaseHelper.java
index 8167b97..1360510 100644
--- a/src/com/android/launcher3/model/DatabaseHelper.java
+++ b/src/com/android/launcher3/model/DatabaseHelper.java
@@ -269,7 +269,6 @@
                                     Favorites.CONTAINER, Favorites.CONTAINER_DESKTOP,
                                     Favorites.CELLY, 0), null);
                 }
-                return;
             }
             case 31: {
                 LauncherDbUtils.migrateLegacyShortcuts(mContext, db);
diff --git a/src/com/android/launcher3/util/RunnableList.java b/src/com/android/launcher3/util/RunnableList.java
index 644537b..f6e0c57 100644
--- a/src/com/android/launcher3/util/RunnableList.java
+++ b/src/com/android/launcher3/util/RunnableList.java
@@ -25,9 +25,7 @@
     private ArrayList<Runnable> mList = null;
     private boolean mDestroyed = false;
 
-    /**
-     * Ads a runnable to this list
-     */
+    /** Adds a runnable to this list */
     public void add(Runnable runnable) {
         if (runnable == null) {
             return;
@@ -42,6 +40,13 @@
         mList.add(runnable);
     }
 
+    /** Removes a previously added runnable */
+    public void remove(Runnable runnable) {
+        if (mList != null) {
+            mList.remove(runnable);
+        }
+    }
+
     /**
      * Destroys the list, executing any pending callbacks. All new callbacks are
      * immediately executed
diff --git a/src_plugins/com/android/systemui/plugins/shared/LauncherOverlayManager.java b/src_plugins/com/android/systemui/plugins/shared/LauncherOverlayManager.java
index 6b27503..54cc0bc 100644
--- a/src_plugins/com/android/systemui/plugins/shared/LauncherOverlayManager.java
+++ b/src_plugins/com/android/systemui/plugins/shared/LauncherOverlayManager.java
@@ -15,16 +15,12 @@
  */
 package com.android.systemui.plugins.shared;
 
-import android.app.Activity;
-import android.app.Application;
-import android.os.Bundle;
-
 import java.io.PrintWriter;
 
 /**
  * Interface to control the overlay on Launcher
  */
-public interface LauncherOverlayManager extends Application.ActivityLifecycleCallbacks {
+public interface LauncherOverlayManager {
 
     default void onDeviceProvideChanged() { }
 
@@ -41,26 +37,15 @@
 
     default void hideOverlay(int duration) { }
 
-    @Override
-    default void onActivityCreated(Activity activity, Bundle bundle) { }
+    default void onActivityStarted() { }
 
-    @Override
-    default void onActivityStarted(Activity activity) { }
+    default void onActivityResumed() { }
 
-    @Override
-    default void onActivityResumed(Activity activity) { }
+    default void onActivityPaused() { }
 
-    @Override
-    default void onActivityPaused(Activity activity) { }
+    default void onActivityStopped() { }
 
-    @Override
-    default void onActivityStopped(Activity activity) { }
-
-    @Override
-    default void onActivitySaveInstanceState(Activity activity, Bundle bundle) { }
-
-    @Override
-    default void onActivityDestroyed(Activity activity) { }
+    default void onActivityDestroyed() { }
 
     interface LauncherOverlay {
 
diff --git a/tests/src/com/android/launcher3/DeleteDropTargetTest.kt b/tests/src/com/android/launcher3/DeleteDropTargetTest.kt
index bcfb90b..46e66e4 100644
--- a/tests/src/com/android/launcher3/DeleteDropTargetTest.kt
+++ b/tests/src/com/android/launcher3/DeleteDropTargetTest.kt
@@ -32,9 +32,9 @@
         buttonDropTarget.setTextMultiLine(false)
 
         // No space for text
-        assertThat(buttonDropTarget.isTextClippedVertically(30)).isTrue()
+        assertThat(buttonDropTarget.isTextClippedVertically(1)).isTrue()
 
         // A lot of space for text so the text should not be clipped
-        assertThat(buttonDropTarget.isTextClippedVertically(100)).isFalse()
+        assertThat(buttonDropTarget.isTextClippedVertically(1000)).isFalse()
     }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
index aa5c770..a85b6bd 100644
--- a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
+++ b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
@@ -366,8 +366,10 @@
         }
         int focusedTaskHeight = mLauncher.getFocusedTaskHeightForTablet();
         for (UiObject2 task : taskViews) {
-            if (task.getVisibleBounds().height() == focusedTaskHeight) {
-                return new OverviewTask(mLauncher, task, this);
+            OverviewTask overviewTask = new OverviewTask(mLauncher, task, this);
+
+            if (overviewTask.getVisibleHeight() == focusedTaskHeight) {
+                return overviewTask;
             }
         }
         return null;
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
index 95a4802..06fac48 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
@@ -36,6 +36,8 @@
  */
 public final class OverviewTask {
     private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
+    private static final String TASK_SNAPSHOT_1 = "snapshot";
+    private static final String TASK_SNAPSHOT_2 = "bottomright_snapshot";
 
     static final Pattern TASK_START_EVENT = Pattern.compile("startActivityFromRecentsAsync");
     static final Pattern SPLIT_SELECT_EVENT = Pattern.compile("enterSplitSelect");
@@ -55,14 +57,64 @@
         mOverview.verifyActiveContainer();
     }
 
+    /**
+     * Returns the height of the visible task, or the combined height of two tasks in split with a
+     * divider between.
+     */
     int getVisibleHeight() {
+        if (isTaskSplit()) {
+            return getCombinedSplitTaskHeight();
+        }
+
         return mTask.getVisibleBounds().height();
     }
 
+    /**
+     * Calculates the visible height for split tasks, containing 2 snapshot tiles and a divider.
+     */
+    private int getCombinedSplitTaskHeight() {
+        UiObject2 taskSnapshot1 =
+                mLauncher.findObjectInContainer(mTask.getParent(), TASK_SNAPSHOT_1);
+        UiObject2 taskSnapshot2 =
+                mLauncher.findObjectInContainer(mTask.getParent(), TASK_SNAPSHOT_2);
+
+        int top = Math.min(
+                taskSnapshot1.getVisibleBounds().top, taskSnapshot2.getVisibleBounds().top);
+        int bottom = Math.max(
+                taskSnapshot1.getVisibleBounds().bottom, taskSnapshot2.getVisibleBounds().bottom);
+
+        return bottom - top;
+    }
+
+    /**
+     * Returns the width of the visible task, or the combined width of two tasks in split with a
+     * divider between.
+     */
     int getVisibleWidth() {
+        if (isTaskSplit()) {
+            return getCombinedSplitTaskWidth();
+        }
+
         return mTask.getVisibleBounds().width();
     }
 
+    /**
+     * Calculates the visible width for split tasks, containing 2 snapshot tiles and a divider.
+     */
+    private int getCombinedSplitTaskWidth() {
+        UiObject2 taskSnapshot1 =
+                mLauncher.findObjectInContainer(mTask.getParent(), TASK_SNAPSHOT_1);
+        UiObject2 taskSnapshot2 =
+                mLauncher.findObjectInContainer(mTask.getParent(), TASK_SNAPSHOT_2);
+
+        int left = Math.min(
+                taskSnapshot1.getVisibleBounds().left, taskSnapshot2.getVisibleBounds().left);
+        int right = Math.max(
+                taskSnapshot1.getVisibleBounds().right, taskSnapshot2.getVisibleBounds().right);
+
+        return right - left;
+    }
+
     int getTaskCenterX() {
         return mTask.getVisibleCenter().x;
     }
diff --git a/tests/tapl/com/android/launcher3/tapl/Qsb.java b/tests/tapl/com/android/launcher3/tapl/Qsb.java
index 5ca80a3..fe2a63d 100644
--- a/tests/tapl/com/android/launcher3/tapl/Qsb.java
+++ b/tests/tapl/com/android/launcher3/tapl/Qsb.java
@@ -22,6 +22,8 @@
 import androidx.test.uiautomator.UiObject2;
 import androidx.test.uiautomator.Until;
 
+import java.util.regex.Pattern;
+
 /**
  * Operations on qsb from either Home screen or AllApp screen.
  */
@@ -30,7 +32,8 @@
     private static final String ASSISTANT_APP_PACKAGE = "com.google.android.googlequicksearchbox";
     private static final String ASSISTANT_ICON_RES_ID = "mic_icon";
     private static final String LENS_ICON_RES_ID = "lens_icon";
-    private static final String LENS_APP_TEXT_RES_ID = "lens_camera_cutout_text";
+    private static final Pattern LENS_APP_RES_PATTERN = Pattern.compile(
+            ASSISTANT_APP_PACKAGE + ":id/lens.*");
     protected final LauncherInstrumentation mLauncher;
     private final UiObject2 mContainer;
     private final String mQsbResName;
@@ -96,8 +99,8 @@
             try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer("clicked")) {
                 // Package name is not enough to check if the app is launched, because many
                 // elements are having googlequicksearchbox as package name. So it checks if the
-                // corresponding text resource is displayed
-                BySelector selector = By.res(ASSISTANT_APP_PACKAGE, LENS_APP_TEXT_RES_ID);
+                // corresponding app resource is displayed
+                BySelector selector = By.res(LENS_APP_RES_PATTERN);
                 mLauncher.assertTrue(
                         "Lens app didn't start: (" + selector + ")",
                         mLauncher.getDevice().wait(Until.hasObject(selector),