Merge "Enable TaskbarEduTooltipControllerTest" into main
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index aafa1f6..ffde0d1 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -471,3 +471,10 @@
     description: "Shows filtered set of widgets by default and an option to show all widgets in the widget picker"
     bug: "356127021"
 }
+
+flag {
+    name: "show_taskbar_pinning_popup_from_anywhere"
+    namespace: "launcher"
+    description: "Shows the pinning popup view after long-pressing or right-clicking anywhere on the pinned taskbar"
+    bug: "297325541"
+}
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 8957e0d..9836172 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -424,6 +424,7 @@
     <!--- Taskbar Pinning -->
     <dimen name="taskbar_pinning_popup_menu_width">300dp</dimen>
     <dimen name="taskbar_pinning_popup_menu_vertical_margin">16dp</dimen>
+    <dimen name="taskbar_pinning_popup_menu_min_padding_from_screen_edge">16dp</dimen>
 
     <!--- Floating Ime Inset height-->
     <dimen name="floating_ime_inset_height">60dp</dimen>
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupView.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupView.kt
index b5a3314..69bc6bd 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupView.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupView.kt
@@ -31,21 +31,21 @@
 import android.widget.Switch
 import androidx.core.view.postDelayed
 import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
+import com.android.launcher3.Flags
 import com.android.launcher3.R
 import com.android.launcher3.popup.ArrowPopup
 import com.android.launcher3.popup.RoundedArrowDrawable
 import com.android.launcher3.util.DisplayController
 import com.android.launcher3.util.Themes
 import com.android.launcher3.views.ActivityContext
+import kotlin.math.max
+import kotlin.math.min
 
 /** Popup view with arrow for taskbar pinning */
 class TaskbarDividerPopupView<T : TaskbarActivityContext>
 @JvmOverloads
-constructor(
-    context: Context,
-    attrs: AttributeSet? = null,
-    defStyleAttr: Int = 0,
-) : ArrowPopup<T>(context, attrs, defStyleAttr) {
+constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
+    ArrowPopup<T>(context, attrs, defStyleAttr) {
     companion object {
         private const val TAG = "TaskbarDividerPopupView"
         private const val DIVIDER_POPUP_CLOSING_DELAY = 333L
@@ -55,24 +55,28 @@
         fun createAndPopulate(
             view: View,
             taskbarActivityContext: TaskbarActivityContext,
+            horizontalPosition: Float,
         ): TaskbarDividerPopupView<*> {
             val taskMenuViewWithArrow =
                 taskbarActivityContext.layoutInflater.inflate(
                     R.layout.taskbar_divider_popup_menu,
                     taskbarActivityContext.dragLayer,
-                    false
+                    false,
                 ) as TaskbarDividerPopupView<*>
 
-            return taskMenuViewWithArrow.populateForView(view)
+            return taskMenuViewWithArrow.populateForView(view, horizontalPosition)
         }
     }
 
     private lateinit var dividerView: View
+    private var horizontalPosition = 0.0f
 
     private val popupCornerRadius = Themes.getDialogCornerRadius(context)
     private val arrowWidth = resources.getDimension(R.dimen.popup_arrow_width)
     private val arrowHeight = resources.getDimension(R.dimen.popup_arrow_height)
     private val arrowPointRadius = resources.getDimension(R.dimen.popup_arrow_corner_radius)
+    private val minPaddingFromScreenEdge =
+        resources.getDimension(R.dimen.taskbar_pinning_popup_menu_min_padding_from_screen_edge)
 
     private var alwaysShowTaskbarOn = !DisplayController.isTransientTaskbar(context)
     private var didPreferenceChange = false
@@ -128,7 +132,15 @@
     /** Orient object as usual and then center object horizontally. */
     override fun orientAboutObject() {
         super.orientAboutObject()
-        x = mTempRect.centerX() - measuredWidth / 2f
+        x =
+            if (Flags.showTaskbarPinningPopupFromAnywhere()) {
+                min(
+                    max(minPaddingFromScreenEdge, horizontalPosition - measuredWidth / 2f),
+                    popupContainer.getWidth() - measuredWidth - minPaddingFromScreenEdge,
+                )
+            } else {
+                mTempRect.centerX() - measuredWidth / 2f
+            }
     }
 
     override fun onControllerInterceptTouchEvent(ev: MotionEvent?): Boolean {
@@ -142,8 +154,9 @@
         return false
     }
 
-    private fun populateForView(view: View): TaskbarDividerPopupView<*> {
+    private fun populateForView(view: View, horizontalPosition: Float): TaskbarDividerPopupView<*> {
         dividerView = view
+        this@TaskbarDividerPopupView.horizontalPosition = horizontalPosition
         tryUpdateBackground()
         return this
     }
@@ -169,15 +182,21 @@
 
     override fun addArrow() {
         super.addArrow()
-        val location = IntArray(2)
-        popupContainer.getLocationInDragLayer(dividerView, location)
-        val dividerViewX = location[0].toFloat()
-        // Change arrow location to the middle of popup.
-        mArrow.x = (dividerViewX + dividerView.width / 2) - (mArrowWidth / 2)
+        if (Flags.showTaskbarPinningPopupFromAnywhere()) {
+            mArrow.x = horizontalPosition - mArrowWidth / 2
+        } else {
+            val location = IntArray(2)
+            popupContainer.getLocationInDragLayer(dividerView, location)
+            val dividerViewX = location[0].toFloat()
+            // Change arrow location to the middle of popup.
+            mArrow.x = (dividerViewX + dividerView.width / 2) - (mArrowWidth / 2)
+        }
     }
 
     override fun updateArrowColor() {
-        if (!Gravity.isVertical(mGravity)) {
+        if (Flags.showTaskbarPinningPopupFromAnywhere()) {
+            super.updateArrowColor()
+        } else if (!Gravity.isVertical(mGravity)) {
             mArrow.background =
                 RoundedArrowDrawable(
                     arrowWidth,
@@ -227,13 +246,13 @@
             ObjectAnimator.ofFloat(
                 this,
                 TRANSLATION_Y,
-                *floatArrayOf(this.translationY, this.translationY + translateYValue)
+                *floatArrayOf(this.translationY, this.translationY + translateYValue),
             )
         val arrowTranslateY =
             ObjectAnimator.ofFloat(
                 mArrow,
                 TRANSLATION_Y,
-                *floatArrayOf(mArrow.translationY, mArrow.translationY + translateYValue)
+                *floatArrayOf(mArrow.translationY, mArrow.translationY + translateYValue),
             )
         val animatorSet = AnimatorSet()
         animatorSet.playTogether(alpha, arrowAlpha, translateY, arrowTranslateY)
@@ -243,7 +262,7 @@
     private fun getAnimatorOfFloat(
         view: View,
         property: Property<View, Float>,
-        vararg values: Float
+        vararg values: Float,
     ): Animator {
         val animator: Animator = ObjectAnimator.ofFloat(view, property, *values)
         animator.setDuration(DIVIDER_POPUP_CLOSING_ANIMATION_DURATION)
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
index 1141a01..55722a9 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
@@ -151,30 +151,26 @@
                 DisplayController.showLockedTaskbarOnHome(context)
         ) {
             // adding the taskbar touch region
-            val touchableHeight: Int
             var left = 0
             var right = context.deviceProfile.widthPx
-            var bubbleBarAdjustment = 0
+            val touchableHeight: Int
             if (uiController.isAnimatingToLauncher) {
                 val dp = controllers.taskbarActivityContext.deviceProfile
                 touchableHeight = windowLayoutParams.height
                 if (dp.isQsbInline) {
                     // if Qsb is inline need to exclude search icon from touch region
                     val isRtl = Utilities.isRtl(context.resources)
-                    bubbleControllers?.bubbleBarViewController?.let {
-                        if (dp.shouldAdjustHotseatOnBubblesLocationUpdate(context)) {
+                    val navBarOffset =
+                        bubbleControllers?.bubbleBarViewController?.let {
                             val isBubblesOnLeft = it.bubbleBarLocation.isOnLeft(isRtl)
-                            bubbleBarAdjustment =
-                                dp.getHotseatTranslationXForBubbleBar(isBubblesOnLeft, isRtl)
-                        }
-                    }
+                            dp.getHotseatTranslationXForNavBar(context, isBubblesOnLeft)
+                        } ?: 0
                     val hotseatPadding: Rect = dp.getHotseatLayoutPadding(context)
                     val borderSpacing: Int = dp.hotseatBorderSpace
                     if (isRtl) {
-                        right =
-                            dp.widthPx - hotseatPadding.right + borderSpacing + bubbleBarAdjustment
+                        right = dp.widthPx - hotseatPadding.right + borderSpacing + navBarOffset
                     } else {
-                        left = hotseatPadding.left - borderSpacing + bubbleBarAdjustment
+                        left = hotseatPadding.left - borderSpacing + navBarOffset
                     }
                 }
             } else {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index 559be36..4bc7d3c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -886,19 +886,18 @@
         mBubbleBarLocation = location;
         if (location == null) {
             // bubble bar is not present, hence no location, resetting the hotseat
-            updateHotseatAndQsbTranslationX(0, animate);
+            updateHotseatAndQsbTranslationX(/* targetValue = */ 0, animate);
             mBubbleBarLocation = null;
             return;
         }
         DeviceProfile deviceProfile = mLauncher.getDeviceProfile();
-        if (!deviceProfile.shouldAdjustHotseatOnBubblesLocationUpdate(
+        if (!deviceProfile.shouldAdjustHotseatOnNavBarLocationUpdate(
                 mControllers.taskbarActivityContext)) {
             return;
         }
-        boolean isRtl = isRtl(mLauncher.getResources());
-        boolean isBubblesOnLeft = location.isOnLeft(isRtl);
+        boolean isBubblesOnLeft = location.isOnLeft(isRtl(mLauncher.getResources()));
         int targetX = deviceProfile
-                .getHotseatTranslationXForBubbleBar(isBubblesOnLeft, isRtl);
+                .getHotseatTranslationXForNavBar(mLauncher, isBubblesOnLeft);
         updateHotseatAndQsbTranslationX(targetX, animate);
     }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarPinningController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarPinningController.kt
index 1867cd0..7848b7e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarPinningController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarPinningController.kt
@@ -76,10 +76,10 @@
             }
     }
 
-    fun showPinningView(view: View) {
+    fun showPinningView(view: View, horizontalPosition: Float = -1f) {
         context.isTaskbarWindowFullscreen = true
         view.post {
-            val popupView = getPopupView(view)
+            val popupView = getPopupView(view, horizontalPosition)
             popupView.requestFocus()
             popupView.onCloseCallback = onCloseCallback
             context.onPopupVisibilityChanged(true)
@@ -89,8 +89,8 @@
     }
 
     @VisibleForTesting
-    fun getPopupView(view: View): TaskbarDividerPopupView<*> {
-        return createAndPopulate(view, context)
+    fun getPopupView(view: View, horizontalPosition: Float = -1f): TaskbarDividerPopupView<*> {
+        return createAndPopulate(view, context, horizontalPosition)
     }
 
     @VisibleForTesting
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index d757180..07ec135 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -316,7 +316,8 @@
         if (mAllAppsButtonContainer != null) {
             mAllAppsButtonContainer.setUpCallbacks(callbacks);
         }
-        if (mTaskbarDividerContainer != null && callbacks.supportsDividerLongPress()) {
+        if (mTaskbarDividerContainer != null
+                && mActivityContext.getTaskbarFeatureEvaluator().getSupportsPinningPopup()) {
             mTaskbarDividerContainer.setUpCallbacks(callbacks);
         }
         if (mTaskbarOverflowView != null) {
@@ -325,6 +326,10 @@
             mTaskbarOverflowView.setOnLongClickListener(
                     mControllerCallbacks.getOverflowOnLongClickListener());
         }
+        if (Flags.showTaskbarPinningPopupFromAnywhere()
+                && mActivityContext.getTaskbarFeatureEvaluator().getSupportsPinningPopup()) {
+            setOnTouchListener(mControllerCallbacks.getTaskbarTouchListener());
+        }
     }
 
     private void removeAndRecycle(View view) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java
index 8bc1e12..4591f9b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java
@@ -19,15 +19,19 @@
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_ALLAPPS_BUTTON_LONG_PRESS;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_ALLAPPS_BUTTON_TAP;
 
+import android.annotation.SuppressLint;
 import android.content.Context;
+import android.view.GestureDetector;
 import android.view.InputDevice;
 import android.view.MotionEvent;
 import android.view.View;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.internal.jank.Cuj;
 import com.android.launcher3.taskbar.bubbles.BubbleBarViewController;
+import com.android.launcher3.util.DisplayController;
 import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
 import com.android.wm.shell.Flags;
 import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
@@ -40,12 +44,14 @@
     private final TaskbarActivityContext mActivity;
     private final TaskbarControllers mControllers;
     private final TaskbarView mTaskbarView;
+    private final GestureDetector mGestureDetector;
 
     public TaskbarViewCallbacks(TaskbarActivityContext activity, TaskbarControllers controllers,
             TaskbarView taskbarView) {
         mActivity = activity;
         mControllers = controllers;
         mTaskbarView = taskbarView;
+        mGestureDetector = new GestureDetector(activity, new TaskbarViewGestureListener());
     }
 
     public View.OnClickListener getIconOnClickListener() {
@@ -70,23 +76,23 @@
         return false;
     }
 
-    public View.OnLongClickListener getTaskbarDividerLongClickListener() {
-        return v -> {
-            mControllers.taskbarPinningController.showPinningView(v);
-            return true;
-        };
+    @SuppressLint("ClickableViewAccessibility")
+    public View.OnTouchListener getTaskbarTouchListener() {
+        return (view, event) -> mGestureDetector.onTouchEvent(event);
     }
 
-    /** Check to see if we support long press on taskbar divider */
-    public boolean supportsDividerLongPress() {
-        return !mActivity.isThreeButtonNav();
+    public View.OnLongClickListener getTaskbarDividerLongClickListener() {
+        return v -> {
+            mControllers.taskbarPinningController.showPinningView(v, getDividerCenterX());
+            return true;
+        };
     }
 
     public View.OnTouchListener getTaskbarDividerRightClickListener() {
         return (v, event) -> {
             if (event.isFromSource(InputDevice.SOURCE_MOUSE)
                     && event.getButtonState() == MotionEvent.BUTTON_SECONDARY) {
-                mControllers.taskbarPinningController.showPinningView(v);
+                mControllers.taskbarPinningController.showPinningView(v, getDividerCenterX());
                 return true;
             }
             return false;
@@ -159,4 +165,32 @@
             }
         };
     }
+
+    private float getDividerCenterX() {
+        View divider = mTaskbarView.getTaskbarDividerViewContainer();
+        if (divider == null) {
+            return 0.0f;
+        }
+        return divider.getX() + (float) divider.getWidth() / 2;
+    }
+
+    private class TaskbarViewGestureListener extends GestureDetector.SimpleOnGestureListener {
+        @Override
+        public boolean onDown(@NonNull MotionEvent event) {
+            return true;
+        }
+
+        @Override
+        public boolean onSingleTapUp(@NonNull MotionEvent event) {
+            return true;
+        }
+
+        @Override
+        public void onLongPress(MotionEvent event) {
+            if (DisplayController.isPinnedTaskbar(mActivity)) {
+                mControllers.taskbarPinningController.showPinningView(mTaskbarView,
+                        event.getRawX());
+            }
+        }
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index c275536..e9458ff 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -837,12 +837,11 @@
 
         int firstRecentTaskIndex = -1;
         int hotseatNavBarTranslationX = 0;
-        if (mCurrentBubbleBarLocation != null
-                && taskbarDp.shouldAdjustHotseatOnBubblesLocationUpdate(mActivity)) {
-            boolean isRtl = mTaskbarView.isLayoutRtl();
-            boolean isBubblesOnLeft = mCurrentBubbleBarLocation.isOnLeft(isRtl);
+        if (mCurrentBubbleBarLocation != null) {
+            boolean isBubblesOnLeft = mCurrentBubbleBarLocation
+                    .isOnLeft(mTaskbarView.isLayoutRtl());
             hotseatNavBarTranslationX = taskbarDp
-                    .getHotseatTranslationXForBubbleBar(isBubblesOnLeft, isRtl);
+                    .getHotseatTranslationXForNavBar(mActivity, isBubblesOnLeft);
         }
         for (int i = 0; i < mTaskbarView.getChildCount(); i++) {
             View child = mTaskbarView.getChildAt(i);
diff --git a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarFeatureEvaluator.kt b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarFeatureEvaluator.kt
index 7739a0e..f130d29 100644
--- a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarFeatureEvaluator.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarFeatureEvaluator.kt
@@ -23,9 +23,7 @@
 
 /** Evaluates all the features taskbar can have. */
 class TaskbarFeatureEvaluator
-private constructor(
-    private val taskbarActivityContext: TaskbarActivityContext,
-) {
+private constructor(private val taskbarActivityContext: TaskbarActivityContext) {
     val hasAllApps = true
     val hasAppIcons = true
     val hasBubbles = false
@@ -43,6 +41,9 @@
     val isLandscape: Boolean
         get() = taskbarActivityContext.deviceProfile.isLandscape
 
+    val supportsPinningPopup: Boolean
+        get() = !hasNavButtons
+
     fun onDestroy() {
         taskbarFeatureEvaluator = null
     }
@@ -51,9 +52,7 @@
         @Volatile private var taskbarFeatureEvaluator: TaskbarFeatureEvaluator? = null
 
         @JvmStatic
-        fun getInstance(
-            taskbarActivityContext: TaskbarActivityContext,
-        ): TaskbarFeatureEvaluator {
+        fun getInstance(taskbarActivityContext: TaskbarActivityContext): TaskbarFeatureEvaluator {
             synchronized(this) {
                 if (taskbarFeatureEvaluator == null) {
                     taskbarFeatureEvaluator = TaskbarFeatureEvaluator(taskbarActivityContext)
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index acdff71..4ad65e1 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -1101,10 +1101,9 @@
         if (isBubbleBarEnabled()
                 && enableBubbleBarInPersistentTaskBar()
                 && mBubbleBarLocation != null) {
-            boolean isRtl = isRtl(getResources());
-            boolean isBubblesOnLeft = mBubbleBarLocation.isOnLeft(isRtl);
+            boolean isBubblesOnLeft = mBubbleBarLocation.isOnLeft(isRtl(getResources()));
             translationX += mDeviceProfile
-                    .getHotseatTranslationXForBubbleBar(isBubblesOnLeft, isRtl);
+                    .getHotseatTranslationXForNavBar(this, isBubblesOnLeft);
         }
         if (isBubbleBarEnabled()
                 && mDeviceProfile.shouldAdjustHotseatForBubbleBar(getContext(), hasBubbles())) {
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 41a8a31..1481ef2 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -607,6 +607,9 @@
             this::createFallbackSwipeHandler;
     private final AbsSwipeUpHandler.Factory mRecentsWindowSwipeHandlerFactory =
             this::createRecentsWindowSwipeHandler;
+    // This needs to be a member to be queued and potentially removed later if the service is
+    // destroyed before the user is unlocked
+    private final Runnable mUserUnlockedRunnable = this::onUserUnlocked;
 
     private final ScreenOnTracker.ScreenOnListener mScreenOnListener = this::onScreenOnChanged;
 
@@ -678,8 +681,7 @@
         mInputConsumer = InputConsumerController.getRecentsAnimationInputConsumer();
 
         // Call runOnUserUnlocked() before any other callbacks to ensure everything is initialized.
-        LockedUserState.get(this).runOnUserUnlocked(this::onUserUnlocked);
-        LockedUserState.get(this).runOnUserUnlocked(mTaskbarManager::onUserUnlocked);
+        LockedUserState.get(this).runOnUserUnlocked(mUserUnlockedRunnable);
         mDeviceState.addNavigationModeChangedCallback(this::onNavigationModeChanged);
         sConnected = true;
 
@@ -746,6 +748,8 @@
 
         mOverviewComponentObserver.setOverviewChangeListener(this::onOverviewTargetChange);
         onOverviewTargetChange(mOverviewComponentObserver.isHomeAndOverviewSame());
+
+        mTaskbarManager.onUserUnlocked();
     }
 
     public OverviewCommandHelper getOverviewCommandHelper() {
@@ -836,6 +840,7 @@
         mDesktopVisibilityController.onDestroy();
         sConnected = false;
 
+        LockedUserState.get(this).removeOnUserUnlockedRunnable(mUserUnlockedRunnable);
         ScreenOnTracker.INSTANCE.get(this).removeListener(mScreenOnListener);
         super.onDestroy();
     }
diff --git a/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java b/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java
index e82d9fc..977c036 100644
--- a/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java
+++ b/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java
@@ -20,7 +20,6 @@
 import com.android.launcher3.dagger.LauncherBaseAppComponent;
 import com.android.launcher3.model.WellbeingModel;
 import com.android.quickstep.util.AsyncClockEventDelegate;
-import com.android.quickstep.util.ContextualSearchHapticManager;
 
 /**
  * Launcher Quickstep base component for Dagger injection.
@@ -35,6 +34,4 @@
     WellbeingModel getWellbeingModel();
 
     AsyncClockEventDelegate getAsyncClockEventDelegate();
-
-    ContextualSearchHapticManager getContextualSearchHapticManager();
 }
diff --git a/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowSwipeHandler.java b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowSwipeHandler.java
index 34b3d74..4f9d837 100644
--- a/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowSwipeHandler.java
@@ -27,7 +27,6 @@
 import static com.android.launcher3.GestureNavContract.EXTRA_REMOTE_CALLBACK;
 import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
 
-import android.animation.ObjectAnimator;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.content.Context;
 import android.content.Intent;
@@ -62,16 +61,15 @@
 import com.android.launcher3.util.DisplayController;
 import com.android.quickstep.AbsSwipeUpHandler;
 import com.android.quickstep.GestureState;
+import com.android.quickstep.RecentsAnimationController;
 import com.android.quickstep.RecentsAnimationDeviceState;
-import com.android.quickstep.RemoteAnimationTargets;
+import com.android.quickstep.RecentsAnimationTargets;
 import com.android.quickstep.TaskAnimationManager;
 import com.android.quickstep.fallback.FallbackRecentsView;
 import com.android.quickstep.fallback.RecentsState;
-import com.android.quickstep.util.ActiveGestureLog;
 import com.android.quickstep.util.RectFSpringAnim;
 import com.android.quickstep.util.SurfaceTransaction.SurfaceProperties;
 import com.android.quickstep.util.TransformParams;
-import com.android.quickstep.util.TransformParams.BuilderProxy;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.Task.TaskKey;
 import com.android.systemui.shared.system.InputConsumerController;
@@ -115,12 +113,25 @@
 
         mRunningOverHome = mGestureState.getRunningTask() != null
                 && mGestureState.getRunningTask().isHomeTask();
-        if (mRunningOverHome) {
-            runActionOnRemoteHandles(remoteTargetHandle ->
-                    remoteTargetHandle.getTransformParams().setHomeBuilderProxy(
-                            RecentsWindowSwipeHandler.
-                                    this::updateHomeActivityTransformDuringSwipeUp));
+
+        initTransformParams();
+    }
+
+    @Override
+    public void onRecentsAnimationStart(RecentsAnimationController controller,
+            RecentsAnimationTargets targets) {
+        super.onRecentsAnimationStart(controller, targets);
+        initTransformParams();
+    }
+
+    private void initTransformParams() {
+        if (mActiveAnimationFactory != null) {
+            mActiveAnimationFactory.initTransformParams();
+            return;
         }
+        runActionOnRemoteHandles(remoteTargetHandle ->
+                remoteTargetHandle.getTransformParams().setHomeBuilderProxy(
+                        RecentsWindowSwipeHandler.this::updateHomeActivityTransformDuringSwipeUp));
     }
 
     @Override
@@ -135,12 +146,17 @@
 
     private void updateHomeActivityTransformDuringSwipeUp(SurfaceProperties builder,
             RemoteAnimationTarget app, TransformParams params) {
-        setHomeScaleAndAlpha(builder, app, mCurrentShift.value,
-                Utilities.boundToRange(1 - mCurrentShift.value, 0, 1));
+        if (mActiveAnimationFactory != null) {
+            return;
+        }
+        setHomeScaleAndAlpha(builder, app, mCurrentShift.value, 0);
     }
 
     private void setHomeScaleAndAlpha(SurfaceProperties builder,
             RemoteAnimationTarget app, float verticalShift, float alpha) {
+        if (app.windowConfiguration.getActivityType() != ACTIVITY_TYPE_HOME) {
+            return;
+        }
         float scale = Utilities.mapRange(verticalShift, 1, mMaxLauncherScale);
         mTmpMatrix.setScale(scale, scale,
                 app.localBounds.exactCenterX(), app.localBounds.exactCenterY());
@@ -163,25 +179,13 @@
         mActiveAnimationFactory = new FallbackHomeAnimationFactory(duration);
         //todo: b/368410893 follow up on this as its intent focused and seems to cut immediately
         Intent intent = new Intent(mGestureState.getHomeIntent());
-        if (mActiveAnimationFactory != null && runningTaskTarget != null) {
+        if (runningTaskTarget != null) {
             mActiveAnimationFactory.addGestureContract(intent, runningTaskTarget.taskInfo);
         }
         return mActiveAnimationFactory;
     }
 
     @Override
-    protected boolean handleTaskAppeared(@NonNull RemoteAnimationTarget[] appearedTaskTargets,
-            @NonNull ActiveGestureLog.CompoundString failureReason) {
-        if (mActiveAnimationFactory != null
-                && mActiveAnimationFactory.handleHomeTaskAppeared(appearedTaskTargets)) {
-            mActiveAnimationFactory = null;
-            return false;
-        }
-
-        return super.handleTaskAppeared(appearedTaskTargets, failureReason);
-    }
-
-    @Override
     protected void finishRecentsControllerToHome(Runnable callback) {
         final Runnable recentsCallback;
         if (mAppCanEnterPip) {
@@ -236,11 +240,12 @@
     private class FallbackHomeAnimationFactory extends HomeAnimationFactory
             implements Consumer<Message> {
         private final Rect mTempRect = new Rect();
-        private final TransformParams mHomeAlphaParams = new TransformParams();
-        private final AnimatedFloat mHomeAlpha;
 
-        private final AnimatedFloat mVerticalShiftForScale = new AnimatedFloat();
-        private final AnimatedFloat mRecentsAlpha = new AnimatedFloat();
+        private final TransformParams mTransformParams = new TransformParams();
+        private final AnimatedFloat mHomeAlpha = new AnimatedFloat(this::updateAppTransforms);
+        private final AnimatedFloat mVerticalShiftForScale =
+                new AnimatedFloat(this::updateAppTransforms);
+        private final AnimatedFloat mRecentsAlpha = new AnimatedFloat(this:: updateAppTransforms);
 
         private final RectF mTargetRect = new RectF();
         private SurfaceControl mSurfaceControl;
@@ -255,25 +260,12 @@
             mDuration = duration;
 
             if (mRunningOverHome) {
-                mHomeAlpha = new AnimatedFloat();
-                mHomeAlpha.value = Utilities.boundToRange(1 - mCurrentShift.value, 0, 1);
                 mVerticalShiftForScale.value = mCurrentShift.value;
-                runActionOnRemoteHandles(remoteTargetHandle ->
-                        remoteTargetHandle.getTransformParams().setHomeBuilderProxy(
-                                FallbackHomeAnimationFactory.this
-                                        ::updateHomeActivityTransformDuringHomeAnim));
-            } else {
-                mHomeAlpha = new AnimatedFloat(this::updateHomeAlpha);
-                mHomeAlpha.value = 0;
-                mHomeAlphaParams.setHomeBuilderProxy(
-                        this::updateHomeActivityTransformDuringHomeAnim);
             }
-
             mRecentsAlpha.value = 1;
-            runActionOnRemoteHandles(remoteTargetHandle ->
-                    remoteTargetHandle.getTransformParams().setBaseBuilderProxy(
-                            FallbackHomeAnimationFactory.this
-                                    ::updateRecentsActivityTransformDuringHomeAnim));
+            mHomeAlpha.value = 0;
+
+            initTransformParams();
         }
 
         @NonNull
@@ -285,63 +277,30 @@
             return mTargetRect;
         }
 
-        private void updateRecentsActivityTransformDuringHomeAnim(SurfaceProperties builder,
-                RemoteAnimationTarget app, TransformParams params) {
-            builder.setAlpha(mRecentsAlpha.value);
-        }
-
-        private void updateHomeActivityTransformDuringHomeAnim(SurfaceProperties builder,
-                RemoteAnimationTarget app, TransformParams params) {
-            setHomeScaleAndAlpha(builder, app, mVerticalShiftForScale.value, mHomeAlpha.value);
-        }
-
         @NonNull
         @Override
         public AnimatorPlaybackController createActivityAnimationToHome() {
             PendingAnimation pa = new PendingAnimation(mDuration);
             pa.setFloat(mRecentsAlpha, AnimatedFloat.VALUE, 0, ACCELERATE);
+            pa.setFloat(mHomeAlpha, AnimatedFloat.VALUE, 1, ACCELERATE);
             return pa.createPlaybackController();
         }
 
-        private void updateHomeAlpha() {
-            if (mHomeAlphaParams.getTargetSet() != null) {
-                mHomeAlphaParams.applySurfaceParams(
-                        mHomeAlphaParams.createSurfaceParams(BuilderProxy.NO_OP));
-            }
-        }
-
-        public boolean handleHomeTaskAppeared(RemoteAnimationTarget[] appearedTaskTargets) {
-            RemoteAnimationTarget appearedTaskTarget = appearedTaskTargets[0];
-            if (appearedTaskTarget.windowConfiguration.getActivityType() == ACTIVITY_TYPE_HOME) {
-                RemoteAnimationTargets targets = new RemoteAnimationTargets(
-                        new RemoteAnimationTarget[] {appearedTaskTarget},
-                        new RemoteAnimationTarget[0], new RemoteAnimationTarget[0],
-                        appearedTaskTarget.mode);
-                mHomeAlphaParams.setTargetSet(targets);
-                updateHomeAlpha();
-                return true;
-            }
-            return false;
-        }
-
         @Override
         public void playAtomicAnimation(float velocity) {
-            ObjectAnimator alphaAnim = mHomeAlpha.animateToValue(mHomeAlpha.value, 1);
-            alphaAnim.setDuration(mDuration).setInterpolator(ACCELERATE);
-            alphaAnim.start();
-
-            if (mRunningOverHome) {
-                // Spring back launcher scale
-                new SpringAnimationBuilder(mContext)
-                        .setStartValue(mVerticalShiftForScale.value)
-                        .setEndValue(0)
-                        .setStartVelocity(-velocity / mTransitionDragLength)
-                        .setMinimumVisibleChange(1f / mDp.heightPx)
-                        .setDampingRatio(0.6f)
-                        .setStiffness(800)
-                        .build(mVerticalShiftForScale, AnimatedFloat.VALUE)
-                        .start();
+            if (!mRunningOverHome) {
+                return;
             }
+            // Spring back launcher scale
+            new SpringAnimationBuilder(mContext)
+                    .setStartValue(mVerticalShiftForScale.value)
+                    .setEndValue(0)
+                    .setStartVelocity(-velocity / mTransitionDragLength)
+                    .setMinimumVisibleChange(1f / mDp.heightPx)
+                    .setDampingRatio(0.6f)
+                    .setStiffness(800)
+                    .build(mVerticalShiftForScale, AnimatedFloat.VALUE)
+                    .start();
         }
 
         @Override
@@ -350,6 +309,34 @@
             mSpringAnim.addAnimatorListener(forEndCallback(this::onRectAnimationEnd));
         }
 
+        private void initTransformParams() {
+            runActionOnRemoteHandles(remoteTargetHandle ->
+                    remoteTargetHandle.getTransformParams().setHomeBuilderProxy(
+                            FallbackHomeAnimationFactory.this
+                                    ::updateHomeActivityTransformDuringHomeAnim));
+
+            mTransformParams.setTargetSet(mRecentsAnimationTargets);
+        }
+
+        private void updateRecentsActivityTransformDuringHomeAnim(SurfaceProperties builder,
+                RemoteAnimationTarget app, TransformParams params) {
+            if (app.mode != mRecentsAnimationTargets.targetMode) {
+                return;
+            }
+            builder.setAlpha(mRecentsAlpha.value);
+        }
+
+        private void updateAppTransforms() {
+            mTransformParams.applySurfaceParams(
+                    mTransformParams.createSurfaceParams(FallbackHomeAnimationFactory.this
+                            ::updateRecentsActivityTransformDuringHomeAnim));
+        }
+
+        private void updateHomeActivityTransformDuringHomeAnim(SurfaceProperties builder,
+                RemoteAnimationTarget app, TransformParams params) {
+            setHomeScaleAndAlpha(builder, app, mVerticalShiftForScale.value, mHomeAlpha.value);
+        }
+
         private void onRectAnimationEnd() {
             mAnimationFinished = true;
             maybeSendEndMessage();
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressHandler.java b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressHandler.java
index 1a825a4..155d095 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressHandler.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressHandler.java
@@ -30,7 +30,6 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
-import com.android.launcher3.LauncherApplication;
 import com.android.launcher3.R;
 import com.android.launcher3.logging.InstanceId;
 import com.android.launcher3.logging.InstanceIdSequence;
@@ -62,8 +61,7 @@
         mContext = context;
         mStatsLogManager = StatsLogManager.newInstance(context);
         mVibratorWrapper = VibratorWrapper.INSTANCE.get(mContext);
-        mContextualSearchHapticManager = ((LauncherApplication) context.getApplicationContext())
-                .getAppComponent().getContextualSearchHapticManager();
+        mContextualSearchHapticManager = ContextualSearchHapticManager.INSTANCE.get(context);
         mContextualSearchInvoker = ContextualSearchInvoker.newInstance(mContext);
     }
 
diff --git a/quickstep/src/com/android/quickstep/util/ContextualSearchHapticManager.kt b/quickstep/src/com/android/quickstep/util/ContextualSearchHapticManager.kt
index 8c246a5..286b77a 100644
--- a/quickstep/src/com/android/quickstep/util/ContextualSearchHapticManager.kt
+++ b/quickstep/src/com/android/quickstep/util/ContextualSearchHapticManager.kt
@@ -21,17 +21,15 @@
 import android.os.VibrationEffect.Composition
 import android.os.Vibrator
 import com.android.launcher3.dagger.ApplicationContext
-import com.android.launcher3.dagger.LauncherAppSingleton
+import com.android.launcher3.util.MainThreadInitializedObject
+import com.android.launcher3.util.SafeCloseable
 import com.android.launcher3.util.VibratorWrapper
 import com.android.quickstep.DeviceConfigWrapper.Companion.get
-import javax.inject.Inject
 import kotlin.math.pow
 
 /** Manages haptics relating to Contextual Search invocations. */
-@LauncherAppSingleton
 class ContextualSearchHapticManager
-@Inject
-internal constructor(@ApplicationContext private val context: Context) {
+internal constructor(@ApplicationContext private val context: Context) : SafeCloseable {
 
     private var searchEffect = createSearchEffect()
     private var contextualSearchStateManager = ContextualSearchStateManager.INSTANCE[context]
@@ -98,4 +96,10 @@
             VibratorWrapper.INSTANCE[context].vibrate(composition.compose())
         }
     }
+
+    override fun close() {}
+
+    companion object {
+        @JvmField val INSTANCE = MainThreadInitializedObject { ContextualSearchHapticManager(it) }
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/util/ContextualSearchInvoker.kt b/quickstep/src/com/android/quickstep/util/ContextualSearchInvoker.kt
index dcb72aa..bd454c0 100644
--- a/quickstep/src/com/android/quickstep/util/ContextualSearchInvoker.kt
+++ b/quickstep/src/com/android/quickstep/util/ContextualSearchInvoker.kt
@@ -22,7 +22,6 @@
 import android.content.Context
 import android.util.Log
 import com.android.internal.app.AssistUtils
-import com.android.launcher3.LauncherApplication
 import com.android.launcher3.R
 import com.android.launcher3.logging.StatsLogManager
 import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_LAUNCH_ASSISTANT_FAILED_SERVICE_ERROR
@@ -57,9 +56,7 @@
         TopTaskTracker.INSTANCE[context],
         SystemUiProxy.INSTANCE[context],
         StatsLogManager.newInstance(context),
-        (context.applicationContext as LauncherApplication)
-            .appComponent
-            .contextualSearchHapticManager,
+        ContextualSearchHapticManager.INSTANCE[context],
         context.getSystemService(ContextualSearchManager::class.java),
     )
 
diff --git a/quickstep/src/com/android/quickstep/util/TransformParams.java b/quickstep/src/com/android/quickstep/util/TransformParams.java
index ebcef30..401eccc 100644
--- a/quickstep/src/com/android/quickstep/util/TransformParams.java
+++ b/quickstep/src/com/android/quickstep/util/TransformParams.java
@@ -143,18 +143,15 @@
         for (int i = 0; i < targets.unfilteredApps.length; i++) {
             RemoteAnimationTarget app = targets.unfilteredApps[i];
             SurfaceProperties builder = transaction.forSurface(app.leash);
+            BuilderProxy targetProxy =
+                    app.windowConfiguration.getActivityType() == ACTIVITY_TYPE_HOME
+                            ? mHomeBuilderProxy
+                            : (app.mode == targets.targetMode ? proxy : mBaseBuilderProxy);
 
             if (app.mode == targets.targetMode) {
-                int activityType = app.windowConfiguration.getActivityType();
-                if (activityType == ACTIVITY_TYPE_HOME) {
-                    mHomeBuilderProxy.onBuildTargetParams(builder, app, this);
-                } else {
-                    builder.setAlpha(getTargetAlpha());
-                    proxy.onBuildTargetParams(builder, app, this);
-                }
-            } else {
-                mBaseBuilderProxy.onBuildTargetParams(builder, app, this);
+                builder.setAlpha(getTargetAlpha());
             }
+            targetProxy.onBuildTargetParams(builder, app, this);
         }
 
         // always put wallpaper layer to bottom.
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index b18af65..5458ba1 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -3810,7 +3810,7 @@
                 }
             } else if (!showAsGrid || (enableLargeDesktopWindowingTile()
                     && dismissedTaskView.isLargeTile()
-                    && nextFocusedTaskView == null)) {
+                    && nextFocusedTaskView == null && !dismissingForSplitSelection)) {
                 int offset = getOffsetToDismissedTask(scrollDiffPerPage, dismissedIndex, taskCount);
                 int scrollDiff = newScroll[i] - oldScroll[i] + offset;
                 if (scrollDiff != 0) {
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 76dc770..5acff06 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -1130,6 +1130,9 @@
                 if (itemInfo.isDisabled()) {
                     setContentDescription(getContext().getString(R.string.disabled_app_label,
                             itemInfo.contentDescription));
+                } else if (itemInfo instanceof WorkspaceItemInfo wai && wai.isArchived()) {
+                    setContentDescription(
+                            getContext().getString(R.string.app_archived_title, itemInfo.title));
                 } else if (hasDot()) {
                     int count = mDotInfo.getNotificationCount();
                     setContentDescription(
@@ -1142,8 +1145,16 @@
     }
 
     private void setDownloadStateContentDescription(ItemInfoWithIcon info, int progressLevel) {
-        if ((info.runtimeStatusFlags & ItemInfoWithIcon.FLAG_ARCHIVED) != 0 && progressLevel == 0) {
-            setContentDescription(getContext().getString(R.string.app_archived_title, info.title));
+        if ((info.runtimeStatusFlags & ItemInfoWithIcon.FLAG_ARCHIVED) != 0
+                && progressLevel == 0) {
+            if (mIcon instanceof PreloadIconDrawable) {
+                // Tell user that download is pending and not to tap to download again.
+                setContentDescription(getContext().getString(
+                        R.string.app_waiting_download_title, info.title));
+            } else {
+                setContentDescription(getContext().getString(
+                        R.string.app_archived_title, info.title));
+            }
         } else if ((info.runtimeStatusFlags & ItemInfoWithIcon.FLAG_SHOW_DOWNLOAD_PROGRESS_MASK)
                 != 0) {
             String percentageString = NumberFormat.getPercentInstance()
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index afe0ee1..8862550 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -2355,18 +2355,23 @@
     /**
      * Returns whether Taskbar and Hotseat should adjust horizontally on bubble bar location update.
      */
-    public boolean shouldAdjustHotseatOnBubblesLocationUpdate(Context context) {
+    public boolean shouldAdjustHotseatOnNavBarLocationUpdate(Context context) {
         return enableBubbleBar()
                 && enableBubbleBarInPersistentTaskBar()
                 && !DisplayController.getNavigationMode(context).hasGestures;
     }
 
     /** Returns hotseat translation X for the bubble bar position. */
-    public int getHotseatTranslationXForBubbleBar(boolean isNavbarOnRight, boolean isRtl) {
-        if (isNavbarOnRight) {
-            return isRtl ? -navButtonsLayoutWidthPx : 0;
+    public int getHotseatTranslationXForNavBar(Context context, boolean isBubblesOnLeft) {
+        if (shouldAdjustHotseatOnNavBarLocationUpdate(context)) {
+            boolean isRtl = Utilities.isRtl(context.getResources());
+            if (isBubblesOnLeft) {
+                return isRtl ? -navButtonsLayoutWidthPx : 0;
+            } else {
+                return isRtl ? 0 : navButtonsLayoutWidthPx;
+            }
         } else {
-            return isRtl ? 0 : navButtonsLayoutWidthPx;
+            return 0;
         }
     }
 
diff --git a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
index de3bb9e..7e3e392 100644
--- a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
+++ b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
@@ -118,8 +118,14 @@
     @Override
     public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
 
-        if (actionId == EditorInfo.IME_ACTION_SEARCH || actionId == EditorInfo.IME_ACTION_GO) {
-            Log.i(TAG, "User tapped ime search button");
+        if (actionId == EditorInfo.IME_ACTION_SEARCH || actionId == EditorInfo.IME_ACTION_GO || (
+                actionId == EditorInfo.IME_NULL && event != null
+                        && event.getAction() == KeyEvent.ACTION_DOWN)) {
+            if (actionId == EditorInfo.IME_NULL) {
+                Log.i(TAG, "User pressed ENTER key");
+            } else {
+                Log.i(TAG, "User tapped ime search button");
+            }
             // selectFocusedView should return SearchTargetEvent that is passed onto onClick
             return mLauncher.getAppsView().getMainAdapterProvider().launchHighlightedItem();
         }
diff --git a/src/com/android/launcher3/util/LockedUserState.kt b/src/com/android/launcher3/util/LockedUserState.kt
index 10559f3..c8d86d4 100644
--- a/src/com/android/launcher3/util/LockedUserState.kt
+++ b/src/com/android/launcher3/util/LockedUserState.kt
@@ -88,6 +88,13 @@
         mUserUnlockedActions.add(action)
     }
 
+    /**
+     * Removes a previously queued `Runnable` to be run when the user is unlocked.
+     */
+    fun removeOnUserUnlockedRunnable(action: Runnable) {
+        mUserUnlockedActions.remove(action)
+    }
+
     companion object {
         @VisibleForTesting
         @JvmField