Merge "Taskbar Pinning Analytics Event" into main
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index aca1b3b..8fddc0f 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -13,3 +13,10 @@
     description: "Enables two line label inside all apps."
     bug: "270390937"
 }
+
+flag {
+    name: "enable_grid_only_overview"
+    namespace: "launcher"
+    description: "Enable a grid-only overview without a focused task."
+    bug: "257950105"
+}
diff --git a/quickstep/res/values-kn/strings.xml b/quickstep/res/values-kn/strings.xml
index 6c84e6f..b7cdd15 100644
--- a/quickstep/res/values-kn/strings.xml
+++ b/quickstep/res/values-kn/strings.xml
@@ -116,7 +116,7 @@
     <string name="taskbar_button_back" msgid="8558862226461164514">"ಹಿಂದೆ"</string>
     <string name="taskbar_button_ime_switcher" msgid="1730244360907588541">"IME ಪರಿವರ್ತಕ"</string>
     <string name="taskbar_button_recents" msgid="7273376136216613134">"ಇತ್ತೀಚಿನವು"</string>
-    <string name="taskbar_button_notifications" msgid="7471740351507357318">"ಅಧಿಸೂಚನೆಗಳು"</string>
+    <string name="taskbar_button_notifications" msgid="7471740351507357318">"ನೋಟಿಫಿಕೇಶನ್‌ಗಳು"</string>
     <string name="taskbar_button_quick_settings" msgid="227662894293189391">"ತ್ವರಿತ ಸೆಟ್ಟಿಂಗ್‍ಗಳು"</string>
     <string name="taskbar_a11y_title" msgid="6432169809852243110">"ಟಾಸ್ಕ್‌ಬಾರ್"</string>
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"ಟಾಸ್ಕ್‌ಬಾರ್ ತೋರಿಸಲಾಗಿದೆ"</string>
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index f32f204..aeb453c 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -317,6 +317,8 @@
     <dimen name="transient_taskbar_stashed_height">32dp</dimen>
     <dimen name="transient_taskbar_all_apps_button_translation_x_offset">4dp</dimen>
     <dimen name="transient_taskbar_stash_spring_velocity_dp_per_s">400dp</dimen>
+    <dimen name="taskbar_tooltip_vertical_padding">8dp</dimen>
+    <dimen name="taskbar_tooltip_horizontal_padding">16dp</dimen>
 
     <!-- An additional touch slop to prevent x-axis movement during the swipe up to show taskbar -->
     <dimen name="transient_taskbar_clamped_offset_bound">16dp</dimen>
@@ -368,6 +370,7 @@
     <dimen name="bubblebar_stashed_handle_height">@dimen/taskbar_stashed_handle_height</dimen>
     <dimen name="bubblebar_pointer_size">8dp</dimen>
     <dimen name="bubblebar_elevation">1dp</dimen>
+    <dimen name="bubblebar_hotseat_adjustment_threshold">90dp</dimen>
 
     <dimen name="bubblebar_icon_size">50dp</dimen>
     <dimen name="bubblebar_badge_size">24dp</dimen>
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 5f0da1c..841fc8f 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -22,6 +22,8 @@
 import static android.provider.Settings.Secure.LAUNCHER_TASKBAR_EDUCATION_SHOWING;
 import static android.view.RemoteAnimationTarget.MODE_CLOSING;
 import static android.view.RemoteAnimationTarget.MODE_OPENING;
+import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_180;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
 import static android.view.WindowManager.TRANSIT_OPEN;
@@ -240,6 +242,8 @@
     private RemoteAnimationFactory mWallpaperOpenTransitionRunner;
     private RemoteTransition mLauncherOpenTransition;
 
+    private final RemoteAnimationCoordinateTransfer mCoordinateTransfer;
+
     private LauncherBackAnimationController mBackAnimationController;
     private final AnimatorListenerAdapter mForceInvisibleListener = new AnimatorListenerAdapter() {
         @Override
@@ -288,6 +292,7 @@
         mOpeningXInterpolator = AnimationUtils.loadInterpolator(context, R.interpolator.app_open_x);
         mOpeningInterpolator = AnimationUtils.loadInterpolator(context,
                 R.interpolator.emphasized_interpolator);
+        mCoordinateTransfer = new RemoteAnimationCoordinateTransfer(mLauncher);
     }
 
     @Override
@@ -1389,8 +1394,9 @@
                         targetRect));
 
         // Hook up floating views to the closing window animators.
-        final int rotationChange = getRotationChange(targets);
-        Rect windowTargetBounds = getWindowTargetBounds(targets, rotationChange);
+        // note the coordinate of closingWindowStartRect is based on launcher
+        Rect windowTargetBounds = new Rect();
+        closingWindowStartRect.round(windowTargetBounds);
         if (floatingIconView != null) {
             anim.addAnimatorListener(floatingIconView);
             floatingIconView.setOnTargetChangeListener(anim::onTargetPositionChanged);
@@ -1694,8 +1700,18 @@
 
             RectF windowTargetBounds =
                     new RectF(getWindowTargetBounds(appTargets, getRotationChange(appTargets)));
+
+            final RectF resolveRectF = new RectF(windowTargetBounds);
+            for (RemoteAnimationTarget t : appTargets) {
+                if (t.mode == MODE_CLOSING) {
+                    transferRectToTargetCoordinate(
+                            t, windowTargetBounds, true, resolveRectF);
+                    break;
+                }
+            }
+
             Pair<RectFSpringAnim, AnimatorSet> pair = createWallpaperOpenAnimations(
-                    appTargets, wallpaperTargets, mFromUnlock, windowTargetBounds,
+                    appTargets, wallpaperTargets, mFromUnlock, resolveRectF,
                     QuickStepContract.getWindowCornerRadius(mLauncher),
                     false /* fromPredictiveBack */);
 
@@ -1941,6 +1957,52 @@
     }
 
     /**
+     * Transfer the rectangle to another coordinate if needed.
+     * @param toLauncher which one is the anchor of this transfer, if true then transfer from
+     *                   animation target to launcher, false transfer from launcher to animation
+     *                   target.
+     */
+    public void transferRectToTargetCoordinate(RemoteAnimationTarget target, RectF currentRect,
+            boolean toLauncher, RectF resultRect) {
+        mCoordinateTransfer.transferRectToTargetCoordinate(
+                target, currentRect, toLauncher, resultRect);
+    }
+
+    private static class RemoteAnimationCoordinateTransfer {
+        private final QuickstepLauncher mLauncher;
+        private final Rect mDisplayRect = new Rect();
+        private final Rect mTmpResult = new Rect();
+
+        RemoteAnimationCoordinateTransfer(QuickstepLauncher launcher) {
+            mLauncher = launcher;
+        }
+
+        void transferRectToTargetCoordinate(RemoteAnimationTarget target, RectF currentRect,
+                boolean toLauncher, RectF resultRect) {
+            final int taskRotation = target.windowConfiguration.getRotation();
+            final DeviceProfile profile = mLauncher.getDeviceProfile();
+
+            final int rotationDelta = toLauncher
+                    ? android.util.RotationUtils.deltaRotation(taskRotation, profile.rotationHint)
+                    : android.util.RotationUtils.deltaRotation(profile.rotationHint, taskRotation);
+            if (rotationDelta != ROTATION_0) {
+                // Get original display size when task is on top but with different rotation
+                if (rotationDelta % 2 != 0 && toLauncher && (profile.rotationHint == ROTATION_0
+                        || profile.rotationHint == ROTATION_180)) {
+                    mDisplayRect.set(0, 0, profile.heightPx, profile.widthPx);
+                } else {
+                    mDisplayRect.set(0, 0, profile.widthPx, profile.heightPx);
+                }
+                currentRect.round(mTmpResult);
+                android.util.RotationUtils.rotateBounds(mTmpResult, mDisplayRect, rotationDelta);
+                resultRect.set(mTmpResult);
+            } else {
+                resultRect.set(currentRect);
+            }
+        }
+    }
+
+    /**
      * RectFSpringAnim update listener to be used for app to home animation.
      */
     private class SpringAnimRunner implements RectFSpringAnim.OnUpdateListener {
@@ -1962,6 +2024,20 @@
             mEndRadius = Math.max(1, targetRect.width()) / 2f;
             mSurfaceApplier = new SurfaceTransactionApplier(mDragLayer);
             mWindowTargetBounds.set(windowTargetBounds);
+
+            // transfer the coordinate based on animation target.
+            if (mAppTargets != null) {
+                for (RemoteAnimationTarget t : mAppTargets) {
+                    if (t.mode == MODE_CLOSING) {
+                        final RectF targetBounds = new RectF(mWindowTargetBounds);
+                        final RectF result = new RectF();
+                        transferRectToTargetCoordinate(
+                                t, targetBounds, false, result);
+                        result.round(mWindowTargetBounds);
+                        break;
+                    }
+                }
+            }
         }
 
         public float getCornerRadius(float progress) {
@@ -1982,6 +2058,8 @@
                 }
 
                 if (target.mode == MODE_CLOSING) {
+                    final RectF before = new RectF(currentRectF);
+                    transferRectToTargetCoordinate(target, currentRectF, false, currentRectF);
                     currentRectF.round(mCurrentRect);
 
                     // Scale the target window to match the currentRectF.
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
index 87a9ecb..a63f9e8 100644
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
@@ -22,6 +22,7 @@
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_PREDICTION_PINNED;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_RANKED;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.FlagDebugUtils.appendFlag;
 
 import android.animation.Animator;
 import android.animation.AnimatorSet;
@@ -61,9 +62,11 @@
 import com.android.launcher3.util.OnboardingPrefs;
 import com.android.launcher3.views.Snackbar;
 
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.StringJoiner;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
 
@@ -185,7 +188,7 @@
     }
 
     private void fillGapsWithPrediction(boolean animate) {
-        Log.d(TAG, "fillGapsWithPrediction");
+        Log.d(TAG, "fillGapsWithPrediction flags: " + getStateString(mPauseFlags));
         if (mPauseFlags != 0) {
             return;
         }
@@ -291,6 +294,7 @@
      * start and pauses predicted apps update on the hotseat
      */
     public void setPauseUIUpdate(boolean paused) {
+        Log.d(TAG, "setPauseUIUpdate parameter `paused` is " + paused);
         mPauseFlags = paused
                 ? (mPauseFlags | FLAG_UPDATE_PAUSED)
                 : (mPauseFlags & ~FLAG_UPDATE_PAUSED);
@@ -515,4 +519,21 @@
                 && ((WorkspaceItemInfo) view.getTag()).container
                 == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
     }
+
+    private static String getStateString(int flags) {
+        StringJoiner str = new StringJoiner("|");
+        appendFlag(str, flags, FLAG_UPDATE_PAUSED, "FLAG_UPDATE_PAUSED");
+        appendFlag(str, flags, FLAG_DRAG_IN_PROGRESS, "FLAG_DRAG_IN_PROGRESS");
+        appendFlag(str, flags, FLAG_FILL_IN_PROGRESS, "FLAG_FILL_IN_PROGRESS");
+        appendFlag(str, flags, FLAG_REMOVING_PREDICTED_ICON,
+                "FLAG_REMOVING_PREDICTED_ICON");
+        return str.toString();
+    }
+
+    public void dump(String prefix, PrintWriter writer) {
+        writer.println(prefix + this.getClass().getSimpleName());
+        writer.println(prefix + "\tFlags: " + getStateString(mPauseFlags));
+        writer.println(prefix + "\tmHotSeatItemsCount: " + mHotSeatItemsCount);
+        writer.println(prefix + "\tmPredictedItems: " + mPredictedItems);
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
index d7a4f76..42e6809 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
@@ -160,7 +160,9 @@
             if (mInOverviewState) {
                 setLauncherViewsVisibility(View.VISIBLE);
                 markLauncherResumed();
-            } else if (mFreeformTasksVisible) {
+            } else if (mFreeformTasksVisible && !mGestureInProgress) {
+                // Switching out of overview state and gesture finished.
+                // If freeform tasks are still visible, hide launcher again.
                 setLauncherViewsVisibility(View.INVISIBLE);
                 markLauncherPaused();
             }
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 4e834ec..e6dfe0f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -41,6 +41,7 @@
 import com.android.launcher3.logging.InstanceId;
 import com.android.launcher3.logging.InstanceIdSequence;
 import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.taskbar.bubbles.BubbleBarController;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.MultiPropertyFactory;
@@ -202,10 +203,18 @@
         return mTaskbarLauncherStateController.applyState(fromInit ? 0 : duration, startAnimation);
     }
 
+    @Override
     public void refreshResumedState() {
         onLauncherResumedOrPaused(mLauncher.hasBeenResumed());
     }
 
+    @Override
+    public void adjustHotseatForBubbleBar(boolean isBubbleBarVisible) {
+        if (mLauncher.getHotseat() != null) {
+            mLauncher.getHotseat().adjustForBubbleBar(isBubbleBarVisible);
+        }
+    }
+
     /**
      * Create Taskbar animation when going from an app to Launcher as part of recents transition.
      * @param toState If known, the state we will end up in when reaching Launcher.
@@ -327,6 +336,21 @@
         return mTaskbarInAppDisplayProgress.value > 0;
     }
 
+    public boolean isBubbleBarEnabled() {
+        return BubbleBarController.BUBBLE_BAR_ENABLED;
+    }
+
+    /** Whether the bubble bar has any bubbles. */
+    public boolean hasBubbles() {
+        if (mControllers == null) {
+            return false;
+        }
+        if (mControllers.bubbleControllers.isEmpty()) {
+            return false;
+        }
+        return mControllers.bubbleControllers.get().bubbleBarViewController.hasBubbles();
+    }
+
     @Override
     public void onExpandPip() {
         super.onExpandPip();
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index cfdf900..0aa02f2 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -27,6 +27,7 @@
 import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE;
 import static com.android.launcher3.AbstractFloatingView.TYPE_TASKBAR_OVERLAY_PROXY;
 import static com.android.launcher3.Utilities.isRunningInTestHarness;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NO_RECREATION;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_OPEN;
 import static com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_DRAGGING;
 import static com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_FULLSCREEN;
@@ -58,6 +59,7 @@
 import android.view.RoundedCorner;
 import android.view.Surface;
 import android.view.View;
+import android.view.WindowInsets;
 import android.view.WindowManager;
 import android.widget.FrameLayout;
 import android.widget.Toast;
@@ -227,20 +229,23 @@
         }
 
         // Construct controllers.
+        RotationButtonController rotationButtonController = new RotationButtonController(this,
+                c.getColor(R.color.floating_rotation_button_light_color),
+                c.getColor(R.color.floating_rotation_button_dark_color),
+                R.drawable.ic_sysbar_rotate_button_ccw_start_0,
+                R.drawable.ic_sysbar_rotate_button_ccw_start_90,
+                R.drawable.ic_sysbar_rotate_button_cw_start_0,
+                R.drawable.ic_sysbar_rotate_button_cw_start_90,
+                () -> getDisplay().getRotation());
+        rotationButtonController.setBgExecutor(Executors.THREAD_POOL_EXECUTOR);
+
         mControllers = new TaskbarControllers(this,
                 new TaskbarDragController(this),
                 buttonController,
                 isDesktopMode
                         ? new DesktopNavbarButtonsViewController(this, navButtonsView)
                         : new NavbarButtonsViewController(this, navButtonsView),
-                new RotationButtonController(this,
-                        c.getColor(R.color.floating_rotation_button_light_color),
-                        c.getColor(R.color.floating_rotation_button_dark_color),
-                        R.drawable.ic_sysbar_rotate_button_ccw_start_0,
-                        R.drawable.ic_sysbar_rotate_button_ccw_start_90,
-                        R.drawable.ic_sysbar_rotate_button_cw_start_0,
-                        R.drawable.ic_sysbar_rotate_button_cw_start_90,
-                        () -> getDisplay().getRotation()),
+                rotationButtonController,
                 new TaskbarDragLayerController(this, mDragLayer),
                 new TaskbarViewController(this, taskbarView),
                 new TaskbarScrimViewController(this, taskbarScrimView),
@@ -300,6 +305,11 @@
         mNavMode = DisplayController.getNavigationMode(this);
     }
 
+    /** Called when the visibility of the bubble bar changed. */
+    public void bubbleBarVisibilityChanged(boolean isVisible) {
+        mControllers.uiController.adjustHotseatForBubbleBar(isVisible);
+        mControllers.taskbarViewController.resetIconAlignmentController();
+    }
 
     public void init(@NonNull TaskbarSharedState sharedState) {
         mImeDrawsImeNavBar = getBoolByName(IME_DRAWS_IME_NAV_BAR_RES_NAME, getResources(), false);
@@ -325,11 +335,11 @@
             mIsDestroyed = false;
         }
 
-        if (!mAddedWindow) {
+        if (!ENABLE_TASKBAR_NO_RECREATION.get() && !mAddedWindow) {
             mWindowManager.addView(mDragLayer, mWindowLayoutParams);
             mAddedWindow = true;
         } else {
-            mWindowManager.updateViewLayout(mDragLayer, mWindowLayoutParams);
+            notifyUpdateLayoutParams();
         }
     }
 
@@ -345,6 +355,11 @@
         mControllers.taskbarAllAppsController.toggle();
     }
 
+    /** Toggles Taskbar All Apps overlay with keyboard ready for search. */
+    public void toggleAllAppsSearch() {
+        mControllers.taskbarAllAppsController.toggleSearch();
+    }
+
     @Override
     public DeviceProfile getDeviceProfile() {
         return mDeviceProfile;
@@ -676,7 +691,7 @@
         mIsDestroyed = true;
         setUIController(TaskbarUIController.DEFAULT);
         mControllers.onDestroy();
-        if (!FLAG_HIDE_NAVBAR_WINDOW) {
+        if (!ENABLE_TASKBAR_NO_RECREATION.get() && !FLAG_HIDE_NAVBAR_WINDOW) {
             mWindowManager.removeViewImmediate(mDragLayer);
             mAddedWindow = false;
         }
@@ -808,7 +823,7 @@
         }
         mWindowLayoutParams.height = height;
         mControllers.taskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged();
-        mWindowManager.updateViewLayout(mDragLayer, mWindowLayoutParams);
+        notifyUpdateLayoutParams();
     }
 
     /**
@@ -851,7 +866,22 @@
         } else {
             mWindowLayoutParams.flags |= FLAG_NOT_FOCUSABLE;
         }
-        mWindowManager.updateViewLayout(mDragLayer, mWindowLayoutParams);
+        notifyUpdateLayoutParams();
+    }
+
+    /**
+     * Applies forcibly show flag to taskbar window iff transient taskbar is unstashed.
+     */
+    public void applyForciblyShownFlagWhileTransientTaskbarUnstashed(boolean shouldForceShow) {
+        if (!DisplayController.isTransientTaskbar(this)) {
+            return;
+        }
+        if (shouldForceShow) {
+            mWindowLayoutParams.forciblyShownTypes |= WindowInsets.Type.navigationBars();
+        } else {
+            mWindowLayoutParams.forciblyShownTypes &= ~WindowInsets.Type.navigationBars();
+        }
+        notifyUpdateLayoutParams();
     }
 
     /**
@@ -960,8 +990,8 @@
                         }
 
                     } catch (NullPointerException
-                            | ActivityNotFoundException
-                            | SecurityException e) {
+                             | ActivityNotFoundException
+                             | SecurityException e) {
                         Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT)
                                 .show();
                         Log.e(TAG, "Unable to launch. tag=" + info + " intent=" + intent, e);
@@ -1055,6 +1085,7 @@
 
     /**
      * Called when we detect a long press in the nav region before passing the gesture slop.
+     *
      * @return Whether taskbar handled the long press, and thus should cancel the gesture.
      */
     public boolean onLongPressToUnstashTaskbar() {
@@ -1124,7 +1155,7 @@
      * Called when we detect a motion down or up/cancel in the nav region while stashed.
      *
      * @param animateForward Whether to animate towards the unstashed hint state or back to stashed.
-     * @param forceUnstash Whether we force the unstash hint.
+     * @param forceUnstash   Whether we force the unstash hint.
      */
     public void startTaskbarUnstashHint(boolean animateForward, boolean forceUnstash) {
         // TODO(b/270395798): Clean up forceUnstash after removing long-press unstashing code.
@@ -1232,12 +1263,16 @@
             mWindowLayoutParams.privateFlags &=
                     ~WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
         }
-        mWindowManager.updateViewLayout(mDragLayer, mWindowLayoutParams);
+        notifyUpdateLayoutParams();
     }
 
     void notifyUpdateLayoutParams() {
         if (mDragLayer.isAttachedToWindow()) {
-            mWindowManager.updateViewLayout(mDragLayer, mWindowLayoutParams);
+            if (ENABLE_TASKBAR_NO_RECREATION.get()) {
+                mWindowManager.updateViewLayout(mDragLayer.getRootView(), mWindowLayoutParams);
+            } else {
+                mWindowManager.updateViewLayout(mDragLayer, mWindowLayoutParams);
+            }
         }
     }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarHoverToolTipController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarHoverToolTipController.java
index c3ec1e5..2c72b2c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarHoverToolTipController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarHoverToolTipController.java
@@ -35,8 +35,6 @@
 import android.view.MotionEvent;
 import android.view.View;
 
-import androidx.annotation.VisibleForTesting;
-
 import com.android.app.animation.Interpolators;
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BubbleTextView;
@@ -51,8 +49,7 @@
  */
 public class TaskbarHoverToolTipController implements View.OnHoverListener {
 
-    @VisibleForTesting protected static final int HOVER_TOOL_TIP_REVEAL_START_DELAY = 400;
-    private static final int HOVER_TOOL_TIP_REVEAL_DURATION = 300;
+    private static final int HOVER_TOOL_TIP_REVEAL_DURATION = 250;
     private static final int HOVER_TOOL_TIP_EXIT_DURATION = 150;
 
     private final Handler mHoverToolTipHandler = new Handler(Looper.getMainLooper());
@@ -84,6 +81,12 @@
                 R.style.ArrowTipTaskbarStyle);
         mHoverToolTipView = new ArrowTipView(arrowContextWrapper, /* isPointingUp = */ false,
                 R.layout.arrow_toast);
+        int verticalPadding = arrowContextWrapper.getResources().getDimensionPixelSize(
+                R.dimen.taskbar_tooltip_vertical_padding);
+        int horizontalPadding = arrowContextWrapper.getResources().getDimensionPixelSize(
+                R.dimen.taskbar_tooltip_horizontal_padding);
+        mHoverToolTipView.findViewById(R.id.text).setPadding(horizontalPadding, verticalPadding,
+                horizontalPadding, verticalPadding);
 
         AnimatorSet hoverCloseAnimator = new AnimatorSet();
         ObjectAnimator textCloseAnimator = ObjectAnimator.ofInt(mHoverToolTipView, TEXT_ALPHA, 0);
@@ -101,17 +104,18 @@
         mHoverToolTipView.setCustomCloseAnimation(hoverCloseAnimator);
 
         AnimatorSet hoverOpenAnimator = new AnimatorSet();
-        ObjectAnimator textOpenAnimator = ObjectAnimator.ofInt(mHoverToolTipView, TEXT_ALPHA, 255);
-        textOpenAnimator.setInterpolator(Interpolators.clampToProgress(LINEAR, 0.33f, 1f));
-        ObjectAnimator scaleOpenAnimator = ObjectAnimator.ofFloat(mHoverToolTipView, SCALE_Y, 1f);
+        ObjectAnimator textOpenAnimator =
+                ObjectAnimator.ofInt(mHoverToolTipView, TEXT_ALPHA, 0, 255);
+        textOpenAnimator.setInterpolator(Interpolators.clampToProgress(LINEAR, 0.15f, 0.75f));
+        ObjectAnimator scaleOpenAnimator =
+                ObjectAnimator.ofFloat(mHoverToolTipView, SCALE_Y, 0f, 1f);
         scaleOpenAnimator.setInterpolator(Interpolators.EMPHASIZED);
-        ObjectAnimator alphaOpenAnimator = ObjectAnimator.ofFloat(mHoverToolTipView, ALPHA, 1f);
-        alphaOpenAnimator.setInterpolator(Interpolators.clampToProgress(LINEAR, 0.1f, 0.33f));
+        ObjectAnimator alphaOpenAnimator = ObjectAnimator.ofFloat(mHoverToolTipView, ALPHA, 0f, 1f);
+        alphaOpenAnimator.setInterpolator(Interpolators.clampToProgress(LINEAR, 0f, 0.33f));
         hoverOpenAnimator.playTogether(
                 scaleOpenAnimator,
                 textOpenAnimator,
                 alphaOpenAnimator);
-        hoverOpenAnimator.setStartDelay(HOVER_TOOL_TIP_REVEAL_START_DELAY);
         hoverOpenAnimator.setDuration(HOVER_TOOL_TIP_REVEAL_DURATION);
         mHoverToolTipView.setCustomOpenAnimation(hoverOpenAnimator);
 
@@ -120,8 +124,6 @@
                     mHoverToolTipView.setPivotY(bottom);
                     mHoverToolTipView.setY(mTaskbarView.getTop() - (bottom - top));
                 });
-        mHoverToolTipView.setScaleY(0f);
-        mHoverToolTipView.setAlpha(0f);
     }
 
     @Override
@@ -137,6 +139,15 @@
             mActivity.setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS, false);
             return true;
         } else if (!isAnyOtherFloatingViewOpen && event.getAction() == ACTION_HOVER_ENTER) {
+            if (!mActivity.isTaskbarWindowFullscreen()) {
+                // First time we want to animate a tooltip open, we set the drag layer to
+                // fullscreen so the tooltip will fit within the window. This causes a layout
+                // pass which will trigger a hover exit and hover enter event while still
+                // hovering the view, so we do not animate open on the first hover enter if we
+                // are not already in fullscreen.
+                mActivity.setTaskbarWindowFullscreen(true);
+                return false;
+            }
             // If hovering above a taskbar icon starts, animate the tooltip open. Do not
             // reveal if any floating views such as folders or edu pop-ups are open.
             startRevealHoverToolTip();
@@ -147,8 +158,7 @@
     }
 
     private void startRevealHoverToolTip() {
-        mHoverToolTipHandler.postDelayed(mRevealHoverToolTipRunnable,
-                HOVER_TOOL_TIP_REVEAL_START_DELAY);
+        mHoverToolTipHandler.post(mRevealHoverToolTipRunnable);
     }
 
     private void revealHoverToolTip() {
@@ -158,14 +168,12 @@
         if (mHoverView instanceof FolderIcon && !((FolderIcon) mHoverView).getIconVisible()) {
             return;
         }
-        mActivity.setTaskbarWindowFullscreen(true);
         Rect iconViewBounds = Utilities.getViewBounds(mHoverView);
         mHoverToolTipView.showAtLocation(mToolTipText, iconViewBounds.centerX(),
                 mTaskbarView.getTop(), /* shouldAutoClose= */ false);
     }
 
     private void startHideHoverToolTip() {
-        mHoverToolTipHandler.removeCallbacks(mRevealHoverToolTipRunnable);
         int accessibilityHideTimeout = AccessibilityManagerCompat.getRecommendedTimeoutMillis(
                 mActivity, /* originalTimeout= */ 0, FLAG_CONTENT_TEXT);
         mHoverToolTipHandler.postDelayed(mHideHoverToolTipRunnable, accessibilityHideTimeout);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
index c51a7ec..eb15cef 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
@@ -15,9 +15,9 @@
  */
 package com.android.launcher3.taskbar
 
-import android.inputmethodservice.InputMethodService.ENABLE_HIDE_IME_CAPTION_BAR
 import android.graphics.Insets
 import android.graphics.Region
+import android.inputmethodservice.InputMethodService.ENABLE_HIDE_IME_CAPTION_BAR
 import android.os.Binder
 import android.os.IBinder
 import android.view.Gravity
@@ -41,6 +41,7 @@
 import com.android.launcher3.DeviceProfile
 import com.android.launcher3.R
 import com.android.launcher3.anim.AlphaUpdateListener
+import com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NO_RECREATION
 import com.android.launcher3.taskbar.TaskbarControllers.LoggableTaskbarController
 import com.android.launcher3.util.DisplayController
 import java.io.PrintWriter
@@ -97,7 +98,14 @@
                 0
             }
 
-        windowLayoutParams.providedInsets = getProvidedInsets(insetsRoundedCornerFlag)
+        windowLayoutParams.providedInsets =
+            if (ENABLE_TASKBAR_NO_RECREATION.get()) {
+                getProvidedInsets(controllers.sharedState!!.insetsFrameProviders!!,
+                        insetsRoundedCornerFlag)
+            } else {
+                getProvidedInsets(insetsRoundedCornerFlag)
+            }
+
         if (!context.isGestureNav) {
             if (windowLayoutParams.paramsForRotation != null) {
                 for (layoutParams in windowLayoutParams.paramsForRotation) {
@@ -154,6 +162,26 @@
     }
 
     /**
+     * This is for when ENABLE_TASKBAR_NO_RECREATION is enabled. We generate one instance of
+     * providedInsets and use it across the entire lifecycle of TaskbarManager. The only thing
+     * we need to reset is nav bar flags based on insetsRoundedCornerFlag.
+     */
+    private fun getProvidedInsets(providedInsets: Array<InsetsFrameProvider>,
+                                  insetsRoundedCornerFlag: Int): Array<InsetsFrameProvider> {
+        val navBarsFlag =
+                (if (context.isGestureNav) FLAG_SUPPRESS_SCRIM else 0) or insetsRoundedCornerFlag
+        for (provider in providedInsets) {
+            if (provider.type == navigationBars()) {
+                provider.setFlags(
+                        navBarsFlag,
+                        FLAG_SUPPRESS_SCRIM or FLAG_INSETS_ROUNDED_CORNER
+                )
+            }
+        }
+        return providedInsets
+    }
+
+    /**
      * The inset types and number of insets provided have to match for both gesture nav and button
      * nav. The values and the order of the elements in array are allowed to differ.
      * Reason being WM does not allow types and number of insets changing for a given window once it
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index 90f7bea..88ae349 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -18,6 +18,7 @@
 import static com.android.app.animation.Interpolators.EMPHASIZED;
 import static com.android.launcher3.taskbar.TaskbarKeyguardController.MASK_ANY_SYSUI_LOCKED;
 import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_APP;
+import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_OVERVIEW;
 import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_STASHED_LAUNCHER_STATE;
 import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_HOME;
 import static com.android.launcher3.util.FlagDebugUtils.appendFlag;
@@ -416,6 +417,9 @@
             controllers.bubbleStashController.setBubblesShowingOnOverview(onOverview);
         });
 
+        mControllers.taskbarStashController.updateStateForFlag(FLAG_IN_OVERVIEW,
+                mLauncherState == LauncherState.OVERVIEW);
+
         AnimatorSet animatorSet = new AnimatorSet();
 
         if (hasAnyFlag(changedFlags, FLAG_LAUNCHER_IN_STATE_TRANSITION)) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index 81cff57..ba2116d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -23,6 +23,7 @@
 import static com.android.launcher3.LauncherPrefs.TASKBAR_PINNING;
 import static com.android.launcher3.LauncherPrefs.TASKBAR_PINNING_KEY;
 import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NO_RECREATION;
 import static com.android.launcher3.util.DisplayController.TASKBAR_NOT_DESTROYED_TAG;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.launcher3.util.FlagDebugUtils.formatFlagChange;
@@ -45,6 +46,8 @@
 import android.provider.Settings;
 import android.util.Log;
 import android.view.Display;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -105,6 +108,9 @@
             Settings.Secure.NAV_BAR_KIDS_MODE);
 
     private final Context mContext;
+    private WindowManager mWindowManager;
+    private FrameLayout mTaskbarRootLayout;
+    private boolean mAddedWindow;
     private final TaskbarNavButtonController mNavButtonController;
     private final ComponentCallbacks mComponentCallbacks;
 
@@ -176,6 +182,10 @@
         Display display =
                 service.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY);
         mContext = service.createWindowContext(display, TYPE_NAVIGATION_BAR_PANEL, null);
+        if (ENABLE_TASKBAR_NO_RECREATION.get()) {
+            mWindowManager = mContext.getSystemService(WindowManager.class);
+            mTaskbarRootLayout = new FrameLayout(mContext);
+        }
         mNavButtonController = new TaskbarNavButtonController(service,
                 SystemUiProxy.INSTANCE.get(mContext), new Handler(),
                 AssistUtils.newInstance(mContext));
@@ -258,10 +268,15 @@
             LauncherPrefs.get(mContext).removeListener(mTaskbarPinningPreferenceChangeListener,
                     TASKBAR_PINNING);
             mTaskbarActivityContext.onDestroy();
-            if (!FLAG_HIDE_NAVBAR_WINDOW) {
+            if (!FLAG_HIDE_NAVBAR_WINDOW || ENABLE_TASKBAR_NO_RECREATION.get()) {
                 mTaskbarActivityContext = null;
             }
         }
+        DeviceProfile dp = mUserUnlocked ?
+                LauncherAppState.getIDP(mContext).getDeviceProfile(mContext) : null;
+        if (dp == null || !isTaskbarPresent(dp)) {
+            removeTaskbarRootViewFromWindow();
+        }
     }
 
     /**
@@ -289,7 +304,7 @@
             return;
         }
 
-        mTaskbarActivityContext.toggleAllApps();
+        mTaskbarActivityContext.toggleAllAppsSearch();
     }
 
     /**
@@ -310,6 +325,7 @@
         mUserUnlocked = true;
         LauncherAppState.getIDP(mContext).addOnChangeListener(mIdpChangeListener);
         recreateTaskbar();
+        addTaskbarRootViewToWindow();
     }
 
     /**
@@ -390,10 +406,9 @@
                 return;
             }
 
-            if (mTaskbarActivityContext == null) {
+            if (ENABLE_TASKBAR_NO_RECREATION.get() || mTaskbarActivityContext == null) {
                 mTaskbarActivityContext = new TaskbarActivityContext(mContext, dp,
-                    mNavButtonController,
-                    mUnfoldProgressProvider);
+                        mNavButtonController, mUnfoldProgressProvider);
             } else {
                 mTaskbarActivityContext.updateDeviceProfile(dp);
             }
@@ -404,6 +419,13 @@
                     createTaskbarUIControllerForActivity(mActivity));
             }
 
+            if (ENABLE_TASKBAR_NO_RECREATION.get()) {
+                addTaskbarRootViewToWindow();
+                mTaskbarRootLayout.removeAllViews();
+                mTaskbarRootLayout.addView(mTaskbarActivityContext.getDragLayer());
+                mTaskbarActivityContext.notifyUpdateLayoutParams();
+            }
+
             // We to wait until user unlocks the device to attach listener.
             LauncherPrefs.get(mContext).addListener(mTaskbarPinningPreferenceChangeListener,
                 TASKBAR_PINNING);
@@ -525,6 +547,22 @@
         }
     }
 
+    private void addTaskbarRootViewToWindow() {
+        if (ENABLE_TASKBAR_NO_RECREATION.get() && !mAddedWindow
+                && mTaskbarActivityContext != null) {
+            mWindowManager.addView(mTaskbarRootLayout,
+                    mTaskbarActivityContext.getWindowLayoutParams());
+            mAddedWindow = true;
+        }
+    }
+
+    private void removeTaskbarRootViewFromWindow() {
+        if (ENABLE_TASKBAR_NO_RECREATION.get() && mAddedWindow) {
+            mWindowManager.removeViewImmediate(mTaskbarRootLayout);
+            mAddedWindow = false;
+        }
+    }
+
     /** Temp logs for b/254119092. */
     public void debugWhyTaskbarNotDestroyed(String debugReason) {
         StringJoiner log = new StringJoiner("\n");
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimViewController.java
index 1c250bf..3a733bf 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimViewController.java
@@ -18,6 +18,7 @@
 import static com.android.launcher3.taskbar.bubbles.BubbleBarController.BUBBLE_BAR_ENABLED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED;
+import static com.android.wm.shell.common.bubbles.BubbleConstants.BUBBLE_EXPANDED_SCRIM_ALPHA;
 
 import android.animation.ObjectAnimator;
 import android.view.animation.Interpolator;
@@ -35,8 +36,6 @@
 public class TaskbarScrimViewController implements TaskbarControllers.LoggableTaskbarController,
         TaskbarControllers.BackgroundRendererController {
 
-    private static final float SCRIM_ALPHA = 0.6f;
-
     private static final Interpolator SCRIM_ALPHA_IN = new PathInterpolator(0.4f, 0f, 1f, 1f);
     private static final Interpolator SCRIM_ALPHA_OUT = new PathInterpolator(0f, 0f, 0.8f, 1f);
 
@@ -78,8 +77,9 @@
         final float scrimAlpha = manageMenuExpanded
                 // When manage menu shows there's the first scrim and second scrim so figure out
                 // what the total transparency would be.
-                ? (SCRIM_ALPHA + (SCRIM_ALPHA * (1 - SCRIM_ALPHA)))
-                : showScrim ? SCRIM_ALPHA : 0;
+                ? (BUBBLE_EXPANDED_SCRIM_ALPHA + (BUBBLE_EXPANDED_SCRIM_ALPHA
+                * (1 - BUBBLE_EXPANDED_SCRIM_ALPHA)))
+                : showScrim ? BUBBLE_EXPANDED_SCRIM_ALPHA : 0;
         showScrim(showScrim, scrimAlpha, skipAnim);
     }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarSharedState.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarSharedState.java
index 66ca7d9..abbd18b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarSharedState.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarSharedState.java
@@ -15,15 +15,28 @@
  */
 package com.android.launcher3.taskbar;
 
+import static android.view.InsetsFrameProvider.SOURCE_DISPLAY;
+import static android.view.WindowInsets.Type.mandatorySystemGestures;
+import static android.view.WindowInsets.Type.navigationBars;
+import static android.view.WindowInsets.Type.systemGestures;
+import static android.view.WindowInsets.Type.tappableElement;
+
 import static com.android.launcher3.taskbar.LauncherTaskbarUIController.DISPLAY_PROGRESS_COUNT;
 
 import android.app.PendingIntent;
+import android.os.Binder;
+import android.os.IBinder;
+import android.view.InsetsFrameProvider;
 
 /**
  * State shared across different taskbar instance
  */
 public class TaskbarSharedState {
 
+    private final IBinder mInsetsOwner = new Binder();
+    private static int INDEX_LEFT = 0;
+    private static int INDEX_RIGHT = 1;
+
     // TaskbarManager#onSystemUiFlagsChanged
     public int sysuiStateFlags;
 
@@ -48,4 +61,14 @@
 
     // Taskbar System Action
     public PendingIntent taskbarSystemActionPendingIntent;
+
+    public final InsetsFrameProvider[] insetsFrameProviders = new InsetsFrameProvider[] {
+            new InsetsFrameProvider(mInsetsOwner, 0, navigationBars()),
+            new InsetsFrameProvider(mInsetsOwner, 0, tappableElement()),
+            new InsetsFrameProvider(mInsetsOwner, 0, mandatorySystemGestures()),
+            new InsetsFrameProvider(mInsetsOwner, INDEX_LEFT, systemGestures())
+                    .setSource(SOURCE_DISPLAY),
+            new InsetsFrameProvider(mInsetsOwner, INDEX_RIGHT, systemGestures())
+                    .setSource(SOURCE_DISPLAY)
+    };
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index b5b453b..ee4db73 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -33,6 +33,7 @@
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.launcher3.util.FlagDebugUtils.appendFlag;
 import static com.android.launcher3.util.FlagDebugUtils.formatFlagChange;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE;
@@ -96,6 +97,7 @@
     public static final int FLAG_STASHED_IN_APP_AUTO = 1 << 9; // Autohide (transient taskbar).
     public static final int FLAG_STASHED_SYSUI = 1 << 10; //  app pinning,...
     public static final int FLAG_STASHED_DEVICE_LOCKED = 1 << 11; // device is locked: keyguard, ...
+    public static final int FLAG_IN_OVERVIEW = 1 << 12; // launcher is in overview
 
     // If any of these flags are enabled, isInApp should return true.
     private static final int FLAGS_IN_APP = FLAG_IN_APP | FLAG_IN_SETUP;
@@ -1015,9 +1017,12 @@
 
         updateStateForFlag(FLAG_STASHED_IN_APP_SYSUI, hasAnyFlag(systemUiStateFlags,
                 SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE));
-        updateStateForFlag(FLAG_STASHED_SYSUI,
-                hasAnyFlag(systemUiStateFlags, SYSUI_STATE_SCREEN_PINNING));
 
+        boolean stashForBubbles = hasAnyFlag(FLAG_IN_OVERVIEW)
+                && hasAnyFlag(systemUiStateFlags, SYSUI_STATE_BUBBLES_EXPANDED)
+                && DisplayController.isTransientTaskbar(mActivity);
+        updateStateForFlag(FLAG_STASHED_SYSUI,
+                hasAnyFlag(systemUiStateFlags, SYSUI_STATE_SCREEN_PINNING) || stashForBubbles);
         boolean isLocked = hasAnyFlag(systemUiStateFlags, MASK_ANY_SYSUI_LOCKED)
                 && !hasAnyFlag(systemUiStateFlags, SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY);
         updateStateForFlag(FLAG_STASHED_DEVICE_LOCKED, isLocked);
@@ -1097,6 +1102,7 @@
                     TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_TRANSIENT_TASKBAR,
                     !hasAnyFlag(FLAG_STASHED_IN_APP_AUTO));
         }
+        mActivity.applyForciblyShownFlagWhileTransientTaskbarUnstashed(!isStashedInApp());
     }
 
     private void notifyStashChange(boolean visible, boolean stashed) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index 6fad655..139e8f0 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -43,7 +43,6 @@
 import com.android.systemui.shared.recents.model.Task;
 
 import java.io.PrintWriter;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.stream.Stream;
 
@@ -336,4 +335,7 @@
                 .stream()
                 .map(mControllers.taskbarPopupController::createSplitShortcutFactory);
     }
+
+    /** Adjusts the hotseat for the bubble bar. */
+    public void adjustHotseatForBubbleBar(boolean isBubbleBarVisible) {}
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 3d22e78..54840f1 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -448,6 +448,11 @@
         }
     }
 
+    /** Resets the icon alignment controller so that it can be recreated again later. */
+    void resetIconAlignmentController() {
+        mIconAlignControllerLazy = null;
+    }
+
     /**
      * Creates an animation for aligning the Taskbar icons with the provided Launcher device profile
      */
@@ -564,9 +569,18 @@
                 continue;
             }
 
-            float hotseatIconCenter = hotseatPadding.left
-                    + (hotseatCellSize + borderSpacing) * positionInHotseat
-                    + hotseatCellSize / 2f;
+            float hotseatAdjustedBorderSpace =
+                    launcherDp.getHotseatAdjustedBorderSpaceForBubbleBar(child.getContext());
+            float hotseatIconCenter;
+            if (bubbleBarHasBubbles() && hotseatAdjustedBorderSpace != 0) {
+                hotseatIconCenter = hotseatPadding.left + hotseatCellSize
+                        + (hotseatCellSize + hotseatAdjustedBorderSpace) * positionInHotseat
+                        + hotseatCellSize / 2f;
+            } else {
+                hotseatIconCenter = hotseatPadding.left
+                        + (hotseatCellSize + borderSpacing) * positionInHotseat
+                        + hotseatCellSize / 2f;
+            }
             float childCenter = (child.getLeft() + child.getRight()) / 2f;
             float toX = hotseatIconCenter - childCenter;
             if (child instanceof Reorderable) {
@@ -588,6 +602,11 @@
         return controller;
     }
 
+    private boolean bubbleBarHasBubbles() {
+        return mControllers.bubbleControllers.isPresent()
+                && mControllers.bubbleControllers.get().bubbleBarViewController.hasBubbles();
+    }
+
     public void onRotationChanged(DeviceProfile deviceProfile) {
         if (!mControllers.uiController.isIconAlignedWithHotseat()) {
             // We only translate on rotation when icon is aligned with hotseat
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
index d786d94..e004acc 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
@@ -128,10 +128,19 @@
 
     /** Toggles visibility of {@link TaskbarAllAppsContainerView} in the overlay window. */
     public void toggle() {
+        toggle(false);
+    }
+
+    /** Toggles visibility of {@link TaskbarAllAppsContainerView} with the keyboard for search. */
+    public void toggleSearch() {
+        toggle(true);
+    }
+
+    private void toggle(boolean showKeyboard) {
         if (isOpen()) {
             mSlideInView.close(true);
         } else {
-            show(true);
+            show(true, showKeyboard);
         }
     }
 
@@ -141,6 +150,10 @@
     }
 
     private void show(boolean animate) {
+        show(animate, false);
+    }
+
+    private void show(boolean animate, boolean showKeyboard) {
         if (mAppsView != null) {
             return;
         }
@@ -166,7 +179,11 @@
             cleanUpOverlay();
         });
         TaskbarAllAppsViewController viewController = new TaskbarAllAppsViewController(
-                mOverlayContext, mSlideInView, mControllers, mSearchSessionController);
+                mOverlayContext,
+                mSlideInView,
+                mControllers,
+                mSearchSessionController,
+                showKeyboard);
 
         viewController.show(animate);
         mAppsView = mOverlayContext.getAppsView();
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java
index 85633e9..3a6d613 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java
@@ -41,12 +41,14 @@
     private final TaskbarStashController mTaskbarStashController;
     private final NavbarButtonsViewController mNavbarButtonsViewController;
     private final TaskbarOverlayController mOverlayController;
+    private final boolean mShowKeyboard;
 
     TaskbarAllAppsViewController(
             TaskbarOverlayContext context,
             TaskbarAllAppsSlideInView slideInView,
             TaskbarControllers taskbarControllers,
-            TaskbarSearchSessionController searchSessionController) {
+            TaskbarSearchSessionController searchSessionController,
+            boolean showKeyboard) {
 
         mContext = context;
         mSlideInView = slideInView;
@@ -54,6 +56,7 @@
         mTaskbarStashController = taskbarControllers.taskbarStashController;
         mNavbarButtonsViewController = taskbarControllers.navbarButtonsViewController;
         mOverlayController = taskbarControllers.taskbarOverlayController;
+        mShowKeyboard = showKeyboard;
 
         mSlideInView.init(new TaskbarAllAppsCallbacks(searchSessionController));
         setUpAppDivider();
@@ -120,6 +123,11 @@
         @Override
         public void onAllAppsTransitionEnd(boolean toAllApps) {
             mSearchSessionController.onAllAppsTransitionEnd(toAllApps);
+            if (toAllApps
+                    && mShowKeyboard
+                    && mAppsView.getSearchUiManager().getEditText() != null) {
+                mAppsView.getSearchUiManager().getEditText().requestFocus();
+            }
         }
 
         /** Invoked on back press, returning {@code true} if the search session handled it. */
@@ -128,7 +136,7 @@
         }
 
         void onAllAppsAnimationPending(PendingAnimation animation, boolean toAllApps) {
-            mSearchSessionController.onAllAppsAnimationPending(animation, toAllApps);
+            mSearchSessionController.onAllAppsAnimationPending(animation, toAllApps, mShowKeyboard);
         }
     }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarSearchSessionController.kt b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarSearchSessionController.kt
index 8a2041f..3d15fbd 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarSearchSessionController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarSearchSessionController.kt
@@ -51,7 +51,11 @@
 
     open fun handleBackInvoked(): Boolean = false
 
-    open fun onAllAppsAnimationPending(animation: PendingAnimation, toAllApps: Boolean) = Unit
+    open fun onAllAppsAnimationPending(
+        animation: PendingAnimation,
+        toAllApps: Boolean,
+        showKeyboard: Boolean,
+    ) = Unit
 
     companion object {
         @JvmStatic
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
index 90f4748..bd11efd 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
@@ -190,8 +190,10 @@
         mBubbleStashedHandleViewController = bubbleControllers.bubbleStashedHandleViewController;
 
         bubbleControllers.runAfterInit(() -> {
-            mBubbleBarViewController.setHiddenForBubbles(!BUBBLE_BAR_ENABLED);
-            mBubbleStashedHandleViewController.setHiddenForBubbles(!BUBBLE_BAR_ENABLED);
+            mBubbleBarViewController.setHiddenForBubbles(
+                    !BUBBLE_BAR_ENABLED || mBubbles.isEmpty());
+            mBubbleStashedHandleViewController.setHiddenForBubbles(
+                    !BUBBLE_BAR_ENABLED || mBubbles.isEmpty());
             mBubbleBarViewController.setUpdateSelectedBubbleAfterCollapse(
                     key -> setSelectedBubble(mBubbles.get(key)));
         });
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
index 5c607c9..065dd58 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
@@ -75,7 +75,7 @@
     // Whether the bar is hidden for a sysui state.
     private boolean mHiddenForSysui;
     // Whether the bar is hidden because there are no bubbles.
-    private boolean mHiddenForNoBubbles;
+    private boolean mHiddenForNoBubbles = true;
     private boolean mShouldShowEducation;
 
     public BubbleBarViewController(TaskbarActivityContext activity, BubbleBarView barView) {
@@ -212,6 +212,7 @@
         if (mHiddenForNoBubbles != hidden) {
             mHiddenForNoBubbles = hidden;
             updateVisibilityForStateChange();
+            mActivity.bubbleBarVisibilityChanged(!hidden);
         }
     }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayController.java b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayController.java
index 2dba263..9126c4b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayController.java
@@ -62,13 +62,13 @@
         @Override
         public void onTaskCreated(int taskId, ComponentName componentName) {
             // Created task will be below existing overlay, so move out of the way.
-            hideWindow();
+            hideWindowOnTaskStackChange();
         }
 
         @Override
         public void onTaskMovedToFront(int taskId) {
             // New front task will be below existing overlay, so move out of the way.
-            hideWindow();
+            hideWindowOnTaskStackChange();
         }
 
         @Override
@@ -79,9 +79,15 @@
             // callback.
             if (mControllers.getSharedState() != null
                     && mControllers.getSharedState().allAppsVisible) {
-                hideWindow();
+                hideWindowOnTaskStackChange();
             }
         }
+
+        private void hideWindowOnTaskStackChange() {
+            // A task was launched while overlay window was open, so stash Taskbar.
+            mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(true);
+            hideWindow();
+        }
     };
 
     private DeviceProfile mLauncherDeviceProfile;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index b50ab97..4220273 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -133,7 +133,6 @@
 import com.android.launcher3.uioverrides.touchcontrollers.TransposedQuickSwitchTouchController;
 import com.android.launcher3.uioverrides.touchcontrollers.TwoButtonNavbarTouchController;
 import com.android.launcher3.util.ActivityOptionsWrapper;
-import com.android.launcher3.util.BackPressHandler;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.Executors;
 import com.android.launcher3.util.IntSet;
@@ -345,9 +344,7 @@
         mHotseatPredictionController.setPauseUIUpdate(getTaskbarUIController() == null);
         RunnableList result = super.startActivitySafely(v, intent, item);
         if (result == null) {
-            if (getTaskbarUIController() == null) {
-                mHotseatPredictionController.setPauseUIUpdate(false);
-            }
+            mHotseatPredictionController.setPauseUIUpdate(false);
         } else {
             result.add(() -> mHotseatPredictionController.setPauseUIUpdate(false));
         }
@@ -693,6 +690,14 @@
         }
 
         super.onPause();
+
+        if (ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
+            // If Launcher pauses before both split apps are selected, exit split screen.
+            if (!mSplitSelectStateController.isBothSplitAppsConfirmed()) {
+                mSplitSelectStateController.getSplitAnimationController()
+                        .playPlaceholderDismissAnim(this);
+            }
+        }
     }
 
     @Override
@@ -1272,6 +1277,16 @@
         return overviewCommandHelper == null || overviewCommandHelper.canStartHomeSafely();
     }
 
+    @Override
+    public boolean isBubbleBarEnabled() {
+        return (mTaskbarUIController != null && mTaskbarUIController.isBubbleBarEnabled());
+    }
+
+    @Override
+    public boolean hasBubbles() {
+        return (mTaskbarUIController != null && mTaskbarUIController.hasBubbles());
+    }
+
     private static final class LauncherTaskViewController extends
             TaskViewTouchController<Launcher> {
 
@@ -1311,5 +1326,8 @@
         if (mAppTransitionManager != null) {
             mAppTransitionManager.dump(prefix + "\t" + RING_APPEAR_ANIMATION_PREFIX, writer);
         }
+        if (mHotseatPredictionController != null) {
+            mHotseatPredictionController.dump(prefix, writer);
+        }
     }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java
index f7bef03..f66bc60 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java
@@ -37,6 +37,7 @@
 import com.android.launcher3.widget.LauncherAppWidgetHostView;
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.widget.LauncherWidgetHolder;
+import com.android.launcher3.widget.custom.CustomWidgetManager;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -237,6 +238,14 @@
     @Override
     public LauncherAppWidgetHostView createView(@NonNull Context context, int appWidgetId,
             @NonNull LauncherAppWidgetProviderInfo appWidget) {
+
+        if (appWidget.isCustomWidget()) {
+            LauncherAppWidgetHostView lahv = new LauncherAppWidgetHostView(context);
+            lahv.setAppWidget(appWidgetId, appWidget);
+            CustomWidgetManager.INSTANCE.get(context).onViewCreated(lahv);
+            return lahv;
+        }
+
         LauncherAppWidgetHostView widgetView = getPendingView(appWidgetId);
         if (widgetView != null) {
             removePendingView(appWidgetId);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
index b9221ee..3e7d45e 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
@@ -72,7 +72,7 @@
 
     @Override
     public boolean isTaskbarStashed(Launcher launcher) {
-        if (FeatureFlags.ENABLE_GRID_ONLY_OVERVIEW.get()) {
+        if (FeatureFlags.enableGridOnlyOverview()) {
             return true;
         }
         return super.isTaskbarStashed(launcher);
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index 5a9d80d..3f88139 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -239,7 +239,7 @@
     public final void calculateTaskSize(Context context, DeviceProfile dp, Rect outRect,
             PagedOrientationHandler orientedState) {
         if (dp.isTablet) {
-            if (FeatureFlags.ENABLE_GRID_ONLY_OVERVIEW.get()) {
+            if (FeatureFlags.enableGridOnlyOverview()) {
                 calculateGridTaskSize(context, dp, outRect, orientedState);
             } else {
                 calculateFocusTaskSize(context, dp, outRect);
@@ -336,7 +336,7 @@
             PagedOrientationHandler orientedState) {
         Resources res = context.getResources();
         Rect potentialTaskRect = new Rect();
-        if (FeatureFlags.ENABLE_GRID_ONLY_OVERVIEW.get()) {
+        if (FeatureFlags.enableGridOnlyOverview()) {
             calculateGridSize(dp, potentialTaskRect);
         } else {
             calculateFocusTaskSize(context, dp, potentialTaskRect);
@@ -368,7 +368,7 @@
     public final void calculateModalTaskSize(Context context, DeviceProfile dp, Rect outRect,
             PagedOrientationHandler orientedState) {
         calculateTaskSize(context, dp, outRect, orientedState);
-        boolean isGridOnlyOverview = dp.isTablet && FeatureFlags.ENABLE_GRID_ONLY_OVERVIEW.get();
+        boolean isGridOnlyOverview = dp.isTablet && FeatureFlags.enableGridOnlyOverview();
         int claimedSpaceBelow = isGridOnlyOverview
                 ? dp.overviewActionsTopMarginPx + dp.overviewActionsHeight + dp.stashedTaskbarHeight
                 : (dp.heightPx - outRect.bottom - dp.getInsets().bottom);
@@ -437,11 +437,13 @@
     }
 
     protected void runOnInitBackgroundStateUI(Runnable callback) {
-        mOnInitBackgroundStateUICallback = callback;
         ACTIVITY_TYPE activity = getCreatedActivity();
         if (activity != null && activity.getStateManager().getState() == mBackgroundState) {
+            callback.run();
             onInitBackgroundStateUI();
+            return;
         }
+        mOnInitBackgroundStateUICallback = callback;
     }
 
     private void onInitBackgroundStateUI() {
diff --git a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
index 857c831..1f8ddf0 100644
--- a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
+++ b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
@@ -404,12 +404,16 @@
         AbstractFloatingView.closeAllOpenViewsExcept(mLauncher, false, TYPE_REBIND_SAFE);
         float cornerRadius = Utilities.mapRange(
                 mBackProgress, mWindowScaleStartCornerRadius, mWindowScaleEndCornerRadius);
+        final RectF resolveRectF = new RectF();
+        mQuickstepTransitionManager.transferRectToTargetCoordinate(
+                mBackTarget, mCurrentRect, true, resolveRectF);
+
         Pair<RectFSpringAnim, AnimatorSet> pair =
                 mQuickstepTransitionManager.createWallpaperOpenAnimations(
                     new RemoteAnimationTarget[]{mBackTarget},
                     new RemoteAnimationTarget[0],
                     false /* fromUnlock */,
-                    mCurrentRect,
+                    resolveRectF,
                     cornerRadius,
                     mBackInProgress /* fromPredictiveBack */);
         startTransitionAnimations(pair.first, pair.second);
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index 42bf1ac..833bf5f 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -78,6 +78,14 @@
      */
     private int mTaskFocusIndexOverride = -1;
 
+    /**
+     * Whether we should incoming toggle commands while a previous toggle command is still ongoing.
+     * This serves as a rate-limiter to prevent overlapping animations that can clobber each other
+     * and prevent clean-up callbacks from running. This thus prevents a recurring set of bugs with
+     * janky recents animations and unresponsive home and overview buttons.
+     */
+    private boolean mWaitForToggleCommandComplete = false;
+
     public OverviewCommandHelper(TouchInteractionService service,
             OverviewComponentObserver observer,
             TaskAnimationManager taskAnimationManager) {
@@ -160,15 +168,20 @@
     private boolean launchTask(RecentsView recents, @Nullable TaskView taskView, CommandInfo cmd) {
         RunnableList callbackList = null;
         if (taskView != null) {
+            mWaitForToggleCommandComplete = true;
             taskView.setEndQuickswitchCuj(true);
             callbackList = taskView.launchTasks();
         }
 
         if (callbackList != null) {
-            callbackList.add(() -> scheduleNextTask(cmd));
+            callbackList.add(() -> {
+                scheduleNextTask(cmd);
+                mWaitForToggleCommandComplete = false;
+            });
             return false;
         } else {
             recents.startHome();
+            mWaitForToggleCommandComplete = false;
             return true;
         }
     }
@@ -178,6 +191,9 @@
      * task is deferred until {@link #scheduleNextTask} is called
      */
     private <T extends StatefulActivity<?>> boolean executeCommand(CommandInfo cmd) {
+        if (mWaitForToggleCommandComplete && cmd.type == TYPE_TOGGLE) {
+            return true;
+        }
         BaseActivityInterface<?, T> activityInterface =
                 mOverviewComponentObserver.getActivityInterface();
         RecentsView recents = activityInterface.getVisibleRecentsView();
@@ -359,6 +375,7 @@
             pw.println("    pendingCommandType=" + mPendingCommands.get(0).type);
         }
         pw.println("  mTaskFocusIndexOverride=" + mTaskFocusIndexOverride);
+        pw.println("  mWaitForToggleCommandComplete=" + mWaitForToggleCommandComplete);
     }
 
     private static class CommandInfo {
diff --git a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
index 031d409..9be9294 100644
--- a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -44,9 +44,7 @@
             }
 
             case TestProtocol.REQUEST_BACKGROUND_TO_OVERVIEW_SWIPE_HEIGHT: {
-                final float swipeHeight =
-                        LayoutUtils.getShelfTrackingDistance(mContext, mDeviceProfile,
-                                PagedOrientationHandler.PORTRAIT);
+                final float swipeHeight = mDeviceProfile.heightPx / 2f;
                 response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) swipeHeight);
                 return response;
             }
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index 0de4ffc..2896fe0 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -331,7 +331,7 @@
      * Finishes the running recents animation.
      * @param forceFinish will synchronously finish the controller
      */
-    private void finishRunningRecentsAnimation(boolean toHome, boolean forceFinish) {
+    public void finishRunningRecentsAnimation(boolean toHome, boolean forceFinish) {
         if (mController != null) {
             ActiveGestureLog.INSTANCE.addLog(
                     /* event= */ "finishRunningRecentsAnimation", toHome);
diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index 076f4b1..bd3ccb7 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -80,7 +80,7 @@
         boolean isInLandscape = orientedState.getTouchRotation() != ROTATION_0;
         boolean isTablet = activity.getDeviceProfile().isTablet;
 
-        boolean isGridOnlyOverview = isTablet && FeatureFlags.ENABLE_GRID_ONLY_OVERVIEW.get();
+        boolean isGridOnlyOverview = isTablet && FeatureFlags.enableGridOnlyOverview();
         // Add overview actions to the menu when in in-place rotate landscape mode, or in
         // grid-only overview.
         if ((!canLauncherRotate && isInLandscape) || isGridOnlyOverview) {
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index c1680de..b12d1c6 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -28,6 +28,7 @@
 import static com.android.launcher3.MotionEventsUtils.isTrackpadMultiFingerSwipe;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_TRACKPAD_GESTURE;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.window.WindowManagerProxy.MIN_TABLET_WIDTH;
 import static com.android.quickstep.GestureState.DEFAULT_STATE;
 import static com.android.quickstep.GestureState.TrackpadGestureType.getTrackpadGestureType;
 import static com.android.quickstep.InputConsumer.TYPE_CURSOR_HOVER;
@@ -68,6 +69,7 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.SystemClock;
+import android.os.Trace;
 import android.util.Log;
 import android.view.Choreographer;
 import android.view.InputDevice;
@@ -1210,7 +1212,9 @@
     }
 
     private void preloadOverview(boolean fromInit) {
+        Trace.beginSection("preloadOverview(fromInit=" + fromInit + ")");
         preloadOverview(fromInit, false);
+        Trace.endSection();
     }
 
     private void preloadOverview(boolean fromInit, boolean forSUWAllSet) {
@@ -1260,7 +1264,10 @@
             // We only care about the existing background activity.
             return;
         }
-        if (mOverviewComponentObserver.canHandleConfigChanges(activity.getComponentName(),
+        Configuration oldConfig = activity.getResources().getConfiguration();
+        boolean isFoldUnfold = isTablet(oldConfig) != isTablet(newConfig);
+        if (!isFoldUnfold && mOverviewComponentObserver.canHandleConfigChanges(
+                activity.getComponentName(),
                 activity.getResources().getConfiguration().diff(newConfig))) {
             // Since navBar gestural height are different between portrait and landscape,
             // can handle orientation changes and refresh navigation gestural region through
@@ -1275,6 +1282,10 @@
         preloadOverview(false /* fromInit */);
     }
 
+    private static boolean isTablet(Configuration config) {
+        return config.smallestScreenWidthDp >= MIN_TABLET_WIDTH;
+    }
+
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] rawArgs) {
         // Dump everything
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
index a9accb7..0b11b00 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
@@ -15,6 +15,8 @@
  */
 package com.android.quickstep.inputconsumers;
 
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
 import android.content.Context;
 import android.view.GestureDetector;
 import android.view.GestureDetector.SimpleOnGestureListener;
@@ -52,7 +54,7 @@
                     if (longPressRunnable != null) {
                         setActive(motionEvent);
 
-                        longPressRunnable.run();
+                        MAIN_EXECUTOR.post(longPressRunnable);
                     }
                 }
             }
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index 4c66504..7e61167 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -417,8 +417,9 @@
     private void finishTouchTracking(MotionEvent ev) {
         TraceHelper.INSTANCE.beginSection(UP_EVT);
 
+        boolean isCanceled = ev.getActionMasked() == ACTION_CANCEL;
         if (mPassedWindowMoveSlop && mInteractionHandler != null) {
-            if (ev.getActionMasked() == ACTION_CANCEL) {
+            if (isCanceled) {
                 mInteractionHandler.onGestureCancelled();
             } else {
                 mVelocityTracker.computeCurrentVelocity(PX_PER_MS);
@@ -440,8 +441,10 @@
             if (mActiveCallbacks != null && mInteractionHandler != null) {
                 if (mTaskAnimationManager.isRecentsAnimationRunning()) {
                     // The animation started, but with no movement, in this case, there will be no
-                    // animateToProgress so we have to manually finish here.
-                    mTaskAnimationManager.finishRunningRecentsAnimation(false /* toHome */);
+                    // animateToProgress so we have to manually finish here. In the case of
+                    // ACTION_CANCEL, someone else may be doing something so finish synchronously.
+                    mTaskAnimationManager.finishRunningRecentsAnimation(false /* toHome */,
+                            isCanceled /* forceFinish */);
                 } else {
                     // The animation hasn't started yet, so insert a replacement handler into the
                     // callbacks which immediately finishes the animation after it starts.
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index be66637..b1daac4 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -87,6 +87,7 @@
     private static final String LATENCY_TAG = "StatsLatencyLog";
     private static final String IMPRESSION_TAG = "StatsImpressionLog";
     private static final boolean IS_VERBOSE = Utilities.isPropertyEnabled(LogConfig.STATSLOG);
+    private static final boolean DEBUG = !Utilities.isRunningInTestHarness();
     private static final InstanceId DEFAULT_INSTANCE_ID = InstanceId.fakeInstanceId(0);
     // LauncherAtom.ItemInfo.getDefaultInstance() should be used but until launcher proto migrates
     // from nano to lite, bake constant to prevent robo test failure.
@@ -326,6 +327,11 @@
             if (!Utilities.ATLEAST_R) {
                 return;
             }
+            if (DEBUG) {
+                String name = (event instanceof Enum) ? ((Enum) event).name() :
+                        event.getId() + "";
+                Log.d(TAG, name);
+            }
             LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
 
             if (mSlice == null && mSliceItem != null) {
diff --git a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
index 4bc41bc..a8a96ce 100644
--- a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
+++ b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
@@ -15,6 +15,8 @@
  */
 package com.android.quickstep.util;
 
+import static com.android.launcher3.testing.shared.TestProtocol.PAUSE_DETECTED_MESSAGE;
+
 import android.content.Context;
 import android.content.res.Resources;
 import android.util.Log;
@@ -201,7 +203,8 @@
             ActiveGestureLog.INSTANCE.addLog(logString);
             boolean isFirstDetectedPause = !mHasEverBeenPaused && mIsPaused;
             if (mIsPaused) {
-                AccessibilityManagerCompat.sendPauseDetectedEventToTest(mContext);
+                AccessibilityManagerCompat.sendTestProtocolEventToTest(mContext,
+                        PAUSE_DETECTED_MESSAGE);
                 mHasEverBeenPaused = true;
             }
             if (mOnMotionPauseListener != null) {
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectDataHolder.kt b/quickstep/src/com/android/quickstep/util/SplitSelectDataHolder.kt
index 69c4197..95f1fbf 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectDataHolder.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectDataHolder.kt
@@ -368,7 +368,8 @@
     }
 
     private fun isInitialTaskIntentSet(): Boolean {
-        return initialTaskId != INVALID_TASK_ID || initialIntent != null
+        return initialTaskId != INVALID_TASK_ID || initialIntent != null ||
+                initialPendingIntent != null
     }
 
     fun getInitialTaskId(): Int {
@@ -404,6 +405,7 @@
         secondUser = null
         initialIntent = null
         secondIntent = null
+        initialPendingIntent = null
         secondPendingIntent = null
         itemInfo = null
         splitEvent = null
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index 25b9bdc..b9ff272 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -343,8 +343,7 @@
             boolean isRtlEnabled = !mIsRecentsRtl;
             mPositionHelper.updateThumbnailMatrix(
                     mThumbnailPosition, mThumbnailData, mTaskRect.width(), mTaskRect.height(),
-                    mDp.widthPx, mDp.heightPx, mDp.taskbarHeight, mDp.isTablet,
-                    mOrientationState.getRecentsActivityRotation(), isRtlEnabled);
+                    mDp.isTablet, mOrientationState.getRecentsActivityRotation(), isRtlEnabled);
             mPositionHelper.getMatrix().invert(mInversePositionMatrix);
             if (DEBUG) {
                 Log.d(TAG, " taskRect: " + mTaskRect);
@@ -353,7 +352,7 @@
 
         float fullScreenProgress = Utilities.boundToRange(this.fullScreenProgress.value, 0, 1);
         mCurrentFullscreenParams.setProgress(fullScreenProgress, recentsViewScale.value,
-                /* taskViewScale= */1f, mTaskRect.width(), mDp, mPositionHelper);
+                /* taskViewScale= */1f);
 
         // Apply thumbnail matrix
         float taskWidth = mTaskRect.width();
diff --git a/quickstep/src/com/android/quickstep/views/ClearAllButton.java b/quickstep/src/com/android/quickstep/views/ClearAllButton.java
index 19ac1f8..cfb4d0d 100644
--- a/quickstep/src/com/android/quickstep/views/ClearAllButton.java
+++ b/quickstep/src/com/android/quickstep/views/ClearAllButton.java
@@ -16,7 +16,7 @@
 
 package com.android.quickstep.views;
 
-import static com.android.launcher3.config.FeatureFlags.ENABLE_GRID_ONLY_OVERVIEW;
+import static com.android.launcher3.config.FeatureFlags.enableGridOnlyOverview;
 
 import android.content.Context;
 import android.util.AttributeSet;
@@ -251,7 +251,7 @@
     private float getOriginalTranslationY() {
         DeviceProfile deviceProfile = mActivity.getDeviceProfile();
         if (deviceProfile.isTablet) {
-            if (ENABLE_GRID_ONLY_OVERVIEW.get()) {
+            if (enableGridOnlyOverview()) {
                 return (getRecentsView().getLastComputedTaskSize().height()
                         + deviceProfile.overviewTaskThumbnailTopMarginPx) / 2.0f
                         + deviceProfile.overviewRowSpacing;
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
index b08e80f..5135345 100644
--- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
@@ -499,8 +499,7 @@
         for (int i = 0; i < mSnapshotViewMap.size(); i++) {
             if (i == 0) {
                 // All snapshots share the same params. Only update it with the first snapshot.
-                updateFullscreenParams(mSnapshotDrawParams,
-                        mSnapshotView.getPreviewPositionHelper());
+                updateFullscreenParams(mSnapshotDrawParams);
             }
             mSnapshotViewMap.valueAt(i).setFullscreenParams(mSnapshotDrawParams);
         }
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index eb7598d..437009f 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -16,7 +16,6 @@
 package com.android.quickstep.views;
 
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
-
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.CLEAR_ALL_BUTTON;
 import static com.android.launcher3.LauncherState.EDIT_MODE;
@@ -136,6 +135,10 @@
     @Override
     public void onStateTransitionStart(LauncherState toState) {
         setOverviewStateEnabled(toState.overviewUi);
+        if (toState.overviewUi) {
+            // If overview is enabled, we want to update at the start
+            updateOverviewStateForDesktop(true);
+        }
         setOverviewGridEnabled(toState.displayOverviewTasksAsGrid(mActivity.getDeviceProfile()));
         setOverviewFullscreenEnabled(toState.getOverviewFullscreenProgress() == 1);
         if (toState == OVERVIEW_MODAL_TASK) {
@@ -162,6 +165,11 @@
             runActionOnRemoteHandles(remoteTargetHandle ->
                     remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(true));
         }
+
+        if (!finalState.overviewUi) {
+            // If overview is disabled, we want to update at the end
+            updateOverviewStateForDesktop(false);
+        }
     }
 
     @Override
@@ -273,4 +281,11 @@
             SystemUiProxy.INSTANCE.get(mActivity).showDesktopApps(mActivity.getDisplayId());
         }
     }
+
+    private void updateOverviewStateForDesktop(boolean enabled) {
+        DesktopVisibilityController controller = mActivity.getDesktopVisibilityController();
+        if (controller != null) {
+            controller.setOverviewStateEnabled(enabled);
+        }
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
index b31791a..9141c99 100644
--- a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
+++ b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
@@ -289,7 +289,7 @@
             return 0;
         }
 
-        if (mDp.isTablet && FeatureFlags.ENABLE_GRID_ONLY_OVERVIEW.get()) {
+        if (mDp.isTablet && FeatureFlags.enableGridOnlyOverview()) {
             return mDp.stashedTaskbarHeight;
         }
 
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 7cf47ce..9b41ccd 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -42,11 +42,12 @@
 import static com.android.launcher3.Utilities.mapToRange;
 import static com.android.launcher3.Utilities.squaredHypot;
 import static com.android.launcher3.Utilities.squaredTouchSlop;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_GRID_ONLY_OVERVIEW;
+import static com.android.launcher3.config.FeatureFlags.enableGridOnlyOverview;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_ACTIONS_SPLIT;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_CLEAR_ALL;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_DISMISS_SWIPE_UP;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_SWIPE_DOWN;
+import static com.android.launcher3.testing.shared.TestProtocol.DISMISS_ANIMATION_ENDS_MESSAGE;
 import static com.android.launcher3.touch.PagedOrientationHandler.CANVAS_TRANSLATE;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
@@ -1782,7 +1783,7 @@
                 newFocusedTaskView = getTaskViewAt(1);
             }
         }
-        mFocusedTaskViewId = newFocusedTaskView != null && !ENABLE_GRID_ONLY_OVERVIEW.get()
+        mFocusedTaskViewId = newFocusedTaskView != null && !enableGridOnlyOverview()
                 ? newFocusedTaskView.getTaskViewId() : INVALID_TASK_ID;
         updateTaskSize();
         if (newFocusedTaskView != null) {
@@ -1900,7 +1901,7 @@
      * Returns the number of tasks in the bottom row of the overview grid.
      */
     public int getBottomRowTaskCountForTablet() {
-        return getTaskViewCount() - mTopRowIdSet.size() - (ENABLE_GRID_ONLY_OVERVIEW.get() ? 0 : 1);
+        return getTaskViewCount() - mTopRowIdSet.size() - (enableGridOnlyOverview() ? 0 : 1);
     }
 
     protected void onTaskStackUpdated() {
@@ -1978,7 +1979,7 @@
         DeviceProfile dp = mActivity.getDeviceProfile();
         setOverviewGridEnabled(
                 mActivity.getStateManager().getState().displayOverviewTasksAsGrid(dp));
-        if (ENABLE_GRID_ONLY_OVERVIEW.get()) {
+        if (enableGridOnlyOverview()) {
             mActionsView.updateHiddenFlags(HIDDEN_ACTIONS_IN_MENU, dp.isTablet);
         }
         setPageSpacing(dp.overviewPageSpacing);
@@ -2140,7 +2141,7 @@
         Rect outRect = new Rect(mLastComputedTaskSize);
         outRect.offset(
                 -(primaryScroll - (selectedPageScroll + getOffsetFromScrollPosition(selectedPage))),
-                (int) (showAsGrid() && ENABLE_GRID_ONLY_OVERVIEW.get() && !isTopRow
+                (int) (showAsGrid() && enableGridOnlyOverview() && !isTopRow
                         ? mTopBottomRowHeightDiff : 0));
         return outRect;
     }
@@ -2726,7 +2727,7 @@
 
         boolean runningTaskTileHidden = mRunningTaskTileHidden;
         setCurrentTask(runningTaskViewId);
-        mFocusedTaskViewId = ENABLE_GRID_ONLY_OVERVIEW.get() ? INVALID_TASK_ID : runningTaskViewId;
+        mFocusedTaskViewId = enableGridOnlyOverview() ? INVALID_TASK_ID : runningTaskViewId;
         runOnPageScrollsInitialized(() -> setCurrentPage(getRunningTaskIndex()));
         setRunningTaskViewShowScreenshot(false);
         setRunningTaskHidden(runningTaskTileHidden);
@@ -2919,7 +2920,7 @@
                     // Desktop task view is hidden, skip it from grid calculations
                     continue;
                 }
-                if (!ENABLE_GRID_ONLY_OVERVIEW.get()) {
+                if (!enableGridOnlyOverview()) {
                     // Only apply x-translation when using legacy overview grid
                     gridTranslations[i] += mIsRtl ? taskWidthAndSpacing : -taskWidthAndSpacing;
                 }
@@ -3432,7 +3433,7 @@
 
             int topGridRowSize = mTopRowIdSet.size();
             int bottomGridRowSize = taskCount - mTopRowIdSet.size()
-                    - (ENABLE_GRID_ONLY_OVERVIEW.get() ? 0 : 1);
+                    - (enableGridOnlyOverview() ? 0 : 1);
             boolean topRowLonger = topGridRowSize > bottomGridRowSize;
             boolean bottomRowLonger = bottomGridRowSize > topGridRowSize;
             boolean dismissedTaskFromTop = mTopRowIdSet.contains(dismissedTaskViewId);
@@ -3445,7 +3446,7 @@
             }
             int longRowWidth = Math.max(topGridRowSize, bottomGridRowSize)
                     * (mLastComputedGridTaskSize.width() + mPageSpacing);
-            if (!ENABLE_GRID_ONLY_OVERVIEW.get() && !isStagingFocusedTask) {
+            if (!enableGridOnlyOverview() && !isStagingFocusedTask) {
                 longRowWidth += mLastComputedTaskSize.width() + mPageSpacing;
             }
 
@@ -3853,7 +3854,7 @@
                     } else {
                         // Update focus task and its size.
                         if (finalIsFocusedTaskDismissed && finalNextFocusedTaskView != null) {
-                            mFocusedTaskViewId = ENABLE_GRID_ONLY_OVERVIEW.get()
+                            mFocusedTaskViewId = enableGridOnlyOverview()
                                     ? INVALID_TASK_ID
                                     : finalNextFocusedTaskView.getTaskViewId();
                             mTopRowIdSet.remove(mFocusedTaskViewId);
@@ -4035,7 +4036,8 @@
     }
 
     protected void onDismissAnimationEnds() {
-        AccessibilityManagerCompat.sendDismissAnimationEndsEventToTest(getContext());
+        AccessibilityManagerCompat.sendTestProtocolEventToTest(getContext(),
+                DISMISS_ANIMATION_ENDS_MESSAGE);
     }
 
     public PendingAnimation createAllTasksDismissAnimation(long duration) {
@@ -4358,7 +4360,7 @@
         int midpoint = runningTask == null ? -1 : indexOfChild(runningTask);
         int modalMidpoint = getCurrentPage();
         boolean isModalGridWithoutFocusedTask =
-                showAsGrid && ENABLE_GRID_ONLY_OVERVIEW.get() && mTaskModalness > 0;
+                showAsGrid && enableGridOnlyOverview() && mTaskModalness > 0;
         if (isModalGridWithoutFocusedTask) {
             modalMidpoint = indexOfChild(mSelectedTask);
         }
@@ -4420,7 +4422,7 @@
                 redrawLiveTile();
             }
 
-            if (showAsGrid && ENABLE_GRID_ONLY_OVERVIEW.get() && child instanceof TaskView) {
+            if (showAsGrid && enableGridOnlyOverview() && child instanceof TaskView) {
                 float totalTranslationY = getVerticalOffsetSize(i, modalOffset);
                 FloatProperty translationPropertyY =
                         ((TaskView) child).getSecondaryTaskOffsetTranslationProperty();
@@ -4529,7 +4531,7 @@
      * @param offsetProgress From 0 to 1 where 0 means no offset and 1 means offset offscreen.
      */
     private float getVerticalOffsetSize(int childIndex, float offsetProgress) {
-        if (offsetProgress == 0 || !(showAsGrid() && ENABLE_GRID_ONLY_OVERVIEW.get())
+        if (offsetProgress == 0 || !(showAsGrid() && enableGridOnlyOverview())
                 || mSelectedTask == null) {
             // Don't bother calculating everything below if we won't offset vertically.
             return 0;
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
index f746203..dff0580 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -558,13 +558,12 @@
                     .getRecentsActivityRotation();
             boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
             mPreviewPositionHelper.updateThumbnailMatrix(mPreviewRect, mThumbnailData,
-                    getMeasuredWidth(), getMeasuredHeight(), dp.widthPx, dp.heightPx,
-                    dp.taskbarHeight, dp.isTablet, currentRotation, isRtl);
+                    getMeasuredWidth(), getMeasuredHeight(), dp.isTablet, currentRotation, isRtl);
 
             mBitmapShader.setLocalMatrix(mPreviewPositionHelper.getMatrix());
             mPaint.setShader(mBitmapShader);
         }
-        getTaskView().updateCurrentFullscreenParams(mPreviewPositionHelper);
+        getTaskView().updateCurrentFullscreenParams();
         invalidate();
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index a2976a8..1bd385e 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -110,7 +110,6 @@
 import com.android.quickstep.util.TransformParams;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
-import com.android.systemui.shared.recents.utilities.PreviewPositionHelper;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.QuickStepContract;
 
@@ -1137,7 +1136,7 @@
         if (dp.isTablet) {
             int alignedOptionIndex = 0;
             if (getRecentsView().isOnGridBottomRow(menuContainer.getTaskView()) && dp.isLandscape) {
-                if (FeatureFlags.ENABLE_GRID_ONLY_OVERVIEW.get()) {
+                if (FeatureFlags.enableGridOnlyOverview()) {
                     // With no focused task, there is less available space below the tasks, so align
                     // the arrow to the third option in the menu.
                     alignedOptionIndex = 2;
@@ -1708,21 +1707,20 @@
     }
 
     protected void updateSnapshotRadius() {
-        updateCurrentFullscreenParams(mSnapshotView.getPreviewPositionHelper());
+        updateCurrentFullscreenParams();
         mSnapshotView.setFullscreenParams(mCurrentFullscreenParams);
     }
 
-    void updateCurrentFullscreenParams(PreviewPositionHelper previewPositionHelper) {
-        updateFullscreenParams(mCurrentFullscreenParams, previewPositionHelper);
+    void updateCurrentFullscreenParams() {
+        updateFullscreenParams(mCurrentFullscreenParams);
     }
 
-    protected void updateFullscreenParams(TaskView.FullscreenDrawParams fullscreenParams,
-            PreviewPositionHelper previewPositionHelper) {
+    protected void updateFullscreenParams(TaskView.FullscreenDrawParams fullscreenParams) {
         if (getRecentsView() == null) {
             return;
         }
         fullscreenParams.setProgress(mFullscreenProgress, getRecentsView().getScaleX(),
-                getScaleX(), getWidth(), mActivity.getDeviceProfile(), previewPositionHelper);
+                getScaleX());
     }
 
     /**
@@ -1893,8 +1891,7 @@
         /**
          * Sets the progress in range [0, 1]
          */
-        public void setProgress(float fullscreenProgress, float parentScale, float taskViewScale,
-                int previewWidth, DeviceProfile dp, PreviewPositionHelper pph) {
+        public void setProgress(float fullscreenProgress, float parentScale, float taskViewScale) {
             mCurrentDrawnCornerRadius =
                     Utilities.mapRange(fullscreenProgress, mCornerRadius, mWindowCornerRadius)
                             / parentScale / taskViewScale;
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarHoverToolTipControllerTest.java b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarHoverToolTipControllerTest.java
index 6c0d44d..1b208a7 100644
--- a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarHoverToolTipControllerTest.java
+++ b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarHoverToolTipControllerTest.java
@@ -18,11 +18,11 @@
 import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
 
 import static com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS;
-import static com.android.launcher3.taskbar.TaskbarHoverToolTipController.HOVER_TOOL_TIP_REVEAL_START_DELAY;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.doAnswer;
@@ -125,16 +125,31 @@
     public void onHover_hoverEnterIcon_revealToolTip() {
         when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_ENTER);
         when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_ENTER);
+        when(taskbarActivityContext.isTaskbarWindowFullscreen()).thenReturn(true);
 
         boolean hoverHandled =
                 mTaskbarHoverToolTipController.onHover(mHoverBubbleTextView, mMotionEvent);
-
-        // Verify fullscreen is not set until the delayed runnable to reveal the tooltip has run
-        verify(taskbarActivityContext, never()).setTaskbarWindowFullscreen(true);
         waitForIdleSync();
+
         assertThat(hoverHandled).isTrue();
         verify(taskbarActivityContext).setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS,
                 true);
+        verify(taskbarActivityContext, never()).setTaskbarWindowFullscreen(anyBoolean());
+    }
+
+    @Test
+    public void onHover_hoverEnterIcon_setFullScreenFirstHover() {
+        when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_ENTER);
+        when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_ENTER);
+        when(taskbarActivityContext.isTaskbarWindowFullscreen()).thenReturn(false);
+
+        boolean hoverHandled =
+                mTaskbarHoverToolTipController.onHover(mHoverBubbleTextView, mMotionEvent);
+        waitForIdleSync();
+
+        assertThat(hoverHandled).isFalse();
+        verify(taskbarActivityContext, never()).setAutohideSuspendFlag(
+                FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS, true);
         verify(taskbarActivityContext).setTaskbarWindowFullscreen(true);
     }
 
@@ -156,17 +171,16 @@
     public void onHover_hoverEnterFolderIcon_revealToolTip() {
         when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_ENTER);
         when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_ENTER);
+        when(taskbarActivityContext.isTaskbarWindowFullscreen()).thenReturn(true);
 
         boolean hoverHandled =
                 mTaskbarHoverToolTipController.onHover(mHoverFolderIcon, mMotionEvent);
-
-        // Verify fullscreen is not set until the delayed runnable to reveal the tooltip has run
-        verify(taskbarActivityContext, never()).setTaskbarWindowFullscreen(true);
         waitForIdleSync();
+
         assertThat(hoverHandled).isTrue();
         verify(taskbarActivityContext).setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS,
                 true);
-        verify(taskbarActivityContext).setTaskbarWindowFullscreen(true);
+        verify(taskbarActivityContext, never()).setTaskbarWindowFullscreen(anyBoolean());
     }
 
     @Test
@@ -222,7 +236,6 @@
     }
 
     private void waitForIdleSync() {
-        mTestableLooper.moveTimeForward(HOVER_TOOL_TIP_REVEAL_START_DELAY + 1);
         mTestableLooper.processAllMessages();
     }
 }
diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
index a67d787..23b5d30 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -61,6 +61,7 @@
 import com.android.launcher3.util.rule.FailureWatcher;
 import com.android.launcher3.util.rule.SamplerRule;
 import com.android.launcher3.util.rule.ScreenRecordRule;
+import com.android.launcher3.util.rule.TestIsolationRule;
 import com.android.launcher3.util.rule.TestStabilityRule;
 import com.android.launcher3.util.rule.ViewCaptureRule;
 import com.android.quickstep.views.RecentsView;
@@ -94,9 +95,6 @@
     public final TestRule mDisableHeadsUpNotification = disableHeadsUpNotification();
 
     @Rule
-    public final TestRule mSetLauncherCommand;
-
-    @Rule
     public final TestRule mOrderSensitiveRules;
 
     @Rule
@@ -116,19 +114,7 @@
             Utilities.enableRunningInTestHarnessForTests();
         }
 
-        final ViewCaptureRule viewCaptureRule = new ViewCaptureRule(
-                RecentsActivity.ACTIVITY_TRACKER::getCreatedActivity);
-        mOrderSensitiveRules = RuleChain
-                .outerRule(new SamplerRule())
-                .around(new NavigationModeSwitchRule(mLauncher))
-                .around(new FailureWatcher(mLauncher, viewCaptureRule::getViewCaptureData))
-                .around(viewCaptureRule);
-
-        mOtherLauncherActivity = context.getPackageManager().queryIntentActivities(
-                getHomeIntentInPackage(context),
-                MATCH_DISABLED_COMPONENTS).get(0).activityInfo;
-
-        mSetLauncherCommand = (base, desc) -> new Statement() {
+        final TestRule setLauncherCommand = (base, desc) -> new Statement() {
             @Override
             public void evaluate() throws Throwable {
                 TestCommandReceiver.callCommand(TestCommandReceiver.ENABLE_TEST_LAUNCHER);
@@ -151,6 +137,22 @@
                 }
             }
         };
+
+        final ViewCaptureRule viewCaptureRule = new ViewCaptureRule(
+                RecentsActivity.ACTIVITY_TRACKER::getCreatedActivity);
+        mOrderSensitiveRules = RuleChain
+                .outerRule(new SamplerRule())
+                .around(new TestStabilityRule())
+                .around(new NavigationModeSwitchRule(mLauncher))
+                .around(new FailureWatcher(mLauncher, viewCaptureRule::getViewCaptureData))
+                .around(viewCaptureRule)
+                .around(new TestIsolationRule(mLauncher))
+                .around(setLauncherCommand);
+
+        mOtherLauncherActivity = context.getPackageManager().queryIntentActivities(
+                getHomeIntentInPackage(context),
+                MATCH_DISABLED_COMPONENTS).get(0).activityInfo;
+
         if (TestHelpers.isInLauncherProcess()) {
             mLauncher.setSystemHealthSupplier(startTime -> TestCommandReceiver.callCommand(
                     TestCommandReceiver.GET_SYSTEM_HEALTH_MESSAGE, startTime.toString()).
diff --git a/quickstep/tests/src/com/android/quickstep/FullscreenDrawParamsTest.kt b/quickstep/tests/src/com/android/quickstep/FullscreenDrawParamsTest.kt
index bb1afdf..db06b6b 100644
--- a/quickstep/tests/src/com/android/quickstep/FullscreenDrawParamsTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/FullscreenDrawParamsTest.kt
@@ -16,17 +16,13 @@
 package com.android.quickstep
 
 import android.content.Context
-import android.graphics.Rect
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.launcher3.FakeInvariantDeviceProfileTest
 import com.android.quickstep.util.TaskCornerRadius
 import com.android.quickstep.views.TaskView.FullscreenDrawParams
-import com.android.systemui.shared.recents.model.ThumbnailData
-import com.android.systemui.shared.recents.utilities.PreviewPositionHelper
 import com.android.systemui.shared.system.QuickStepContract
 import com.google.common.truth.Truth.assertThat
-import kotlin.math.roundToInt
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -39,10 +35,6 @@
 @RunWith(AndroidJUnit4::class)
 class FullscreenDrawParamsTest : FakeInvariantDeviceProfileTest() {
 
-    private val TASK_SCALE = 0.7f
-    private var mThumbnailData: ThumbnailData = mock(ThumbnailData::class.java)
-
-    private val mPreviewPositionHelper = PreviewPositionHelper()
     private lateinit var params: FullscreenDrawParams
 
     @Before
@@ -53,32 +45,11 @@
     @Test
     fun setStartProgress_correctCornerRadiusForTablet() {
         initializeVarsForTablet()
-        val dp = newDP()
-        val previewRect = Rect(0, 0, 100, 100)
-        val canvasWidth = (dp.widthPx * TASK_SCALE).roundToInt()
-        val canvasHeight = (dp.heightPx * TASK_SCALE).roundToInt()
-        val currentRotation = 0
-        val isRtl = false
 
-        mPreviewPositionHelper.updateThumbnailMatrix(
-            previewRect,
-            mThumbnailData,
-            canvasWidth,
-            canvasHeight,
-            dp.widthPx,
-            dp.heightPx,
-            dp.taskbarHeight,
-            dp.isTablet,
-            currentRotation,
-            isRtl
-        )
         params.setProgress(
             /* fullscreenProgress= */ 0f,
             /* parentScale= */ 1.0f,
-            /* taskViewScale= */ 1.0f,
-            /* previewWidth= */ 0,
-            dp,
-            mPreviewPositionHelper
+            /* taskViewScale= */ 1.0f
         )
 
         val expectedRadius = TaskCornerRadius.get(context)
@@ -88,32 +59,11 @@
     @Test
     fun setFullProgress_correctCornerRadiusForTablet() {
         initializeVarsForTablet()
-        val dp = newDP()
-        val previewRect = Rect(0, 0, 100, 100)
-        val canvasWidth = (dp.widthPx * TASK_SCALE).roundToInt()
-        val canvasHeight = (dp.heightPx * TASK_SCALE).roundToInt()
-        val currentRotation = 0
-        val isRtl = false
 
-        mPreviewPositionHelper.updateThumbnailMatrix(
-            previewRect,
-            mThumbnailData,
-            canvasWidth,
-            canvasHeight,
-            dp.widthPx,
-            dp.heightPx,
-            dp.taskbarHeight,
-            dp.isTablet,
-            currentRotation,
-            isRtl
-        )
         params.setProgress(
             /* fullscreenProgress= */ 1.0f,
             /* parentScale= */ 1.0f,
-            /* taskViewScale= */ 1.0f,
-            /* previewWidth= */ 0,
-            dp,
-            mPreviewPositionHelper
+            /* taskViewScale= */ 1.0f
         )
 
         val expectedRadius = QuickStepContract.getWindowCornerRadius(context)
@@ -123,32 +73,11 @@
     @Test
     fun setStartProgress_correctCornerRadiusForPhone() {
         initializeVarsForPhone()
-        val dp = newDP()
-        val previewRect = Rect(0, 0, 100, 100)
-        val canvasWidth = (dp.widthPx * TASK_SCALE).roundToInt()
-        val canvasHeight = (dp.heightPx * TASK_SCALE).roundToInt()
-        val currentRotation = 0
-        val isRtl = false
 
-        mPreviewPositionHelper.updateThumbnailMatrix(
-            previewRect,
-            mThumbnailData,
-            canvasWidth,
-            canvasHeight,
-            dp.widthPx,
-            dp.heightPx,
-            dp.taskbarHeight,
-            dp.isTablet,
-            currentRotation,
-            isRtl
-        )
         params.setProgress(
             /* fullscreenProgress= */ 0f,
             /* parentScale= */ 1.0f,
-            /* taskViewScale= */ 1.0f,
-            /* previewWidth= */ 0,
-            dp,
-            mPreviewPositionHelper
+            /* taskViewScale= */ 1.0f
         )
 
         val expectedRadius = TaskCornerRadius.get(context)
@@ -158,32 +87,11 @@
     @Test
     fun setFullProgress_correctCornerRadiusForPhone() {
         initializeVarsForPhone()
-        val dp = newDP()
-        val previewRect = Rect(0, 0, 100, 100)
-        val canvasWidth = (dp.widthPx * TASK_SCALE).roundToInt()
-        val canvasHeight = (dp.heightPx * TASK_SCALE).roundToInt()
-        val currentRotation = 0
-        val isRtl = false
 
-        mPreviewPositionHelper.updateThumbnailMatrix(
-            previewRect,
-            mThumbnailData,
-            canvasWidth,
-            canvasHeight,
-            dp.widthPx,
-            dp.heightPx,
-            dp.taskbarHeight,
-            dp.isTablet,
-            currentRotation,
-            isRtl
-        )
         params.setProgress(
             /* fullscreenProgress= */ 1.0f,
             /* parentScale= */ 1.0f,
-            /* taskViewScale= */ 1.0f,
-            /* previewWidth= */ 0,
-            dp,
-            mPreviewPositionHelper
+            /* taskViewScale= */ 1.0f
         )
 
         val expectedRadius = QuickStepContract.getWindowCornerRadius(context)
@@ -207,10 +115,7 @@
         spyParams.setProgress(
             /* fullscreenProgress= */ 0f,
             /* parentScale= */ 1.0f,
-            /* taskViewScale= */ 1.0f,
-            /* unused previewWidth= */ -1,
-            /* unusedDp= */ null,
-            /* unused previewPositionHelper= */ null
+            /* taskViewScale= */ 1.0f
         )
         assertThat(spyParams.mCurrentDrawnCornerRadius).isEqualTo(display1TaskRadius)
 
@@ -218,10 +123,7 @@
         spyParams.setProgress(
             /* fullscreenProgress= */ 0f,
             /* parentScale= */ 1.0f,
-            /* taskViewScale= */ 1.0f,
-            /* unused previewWidth= */ -1,
-            /* unusedDp= */ null,
-            /* unused previewPositionHelper= */ null
+            /* taskViewScale= */ 1.0f
         )
         assertThat(spyParams.mCurrentDrawnCornerRadius).isEqualTo(display2TaskRadius)
     }
@@ -243,10 +145,7 @@
         spyParams.setProgress(
             /* fullscreenProgress= */ 1.0f,
             /* parentScale= */ 1.0f,
-            /* taskViewScale= */ 1.0f,
-            /* unused previewWidth= */ -1,
-            /* unusedDp= */ null,
-            /* unused previewPositionHelper= */ null
+            /* taskViewScale= */ 1.0f
         )
         assertThat(spyParams.mCurrentDrawnCornerRadius).isEqualTo(display1WindowRadius)
 
@@ -255,9 +154,6 @@
             /* fullscreenProgress= */ 1.0f,
             /* parentScale= */ 1.0f,
             /* taskViewScale= */ 1.0f,
-            /* unused previewWidth= */ -1,
-            /* unusedDp= */ null,
-            /* unused previewPositionHelper= */ null
         )
         assertThat(spyParams.mCurrentDrawnCornerRadius).isEqualTo(display2WindowRadius)
     }
diff --git a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
index 2752003..eded1c9 100644
--- a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
+++ b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
@@ -26,10 +26,7 @@
 
 import android.content.Context;
 import android.content.pm.PackageManager;
-import android.graphics.Point;
-import android.os.SystemClock;
 import android.util.Log;
-import android.view.MotionEvent;
 
 import androidx.test.uiautomator.UiDevice;
 
@@ -118,18 +115,6 @@
                 private void evaluateWithThreeButtons() throws Throwable {
                     if (setActiveOverlay(mLauncher, NAV_BAR_MODE_3BUTTON_OVERLAY,
                             LauncherInstrumentation.NavigationModel.THREE_BUTTON, description)) {
-                        // After switching to three button, ensure that we interact with the screen
-                        // within the app area to reset the frozen recents state (if a quickswitch
-                        // was just performed), otherwise the list may be in the wrong order
-                        // spatially when executing the next test
-                        final Point center = new Point(mLauncher.getDevice().getDisplayWidth() / 2,
-                                mLauncher.getDevice().getDisplayHeight() / 2);
-                        final long clickTime = SystemClock.uptimeMillis();
-                        mLauncher.sendPointer(clickTime, clickTime, MotionEvent.ACTION_DOWN, center,
-                                LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER);
-                        mLauncher.sendPointer(clickTime, clickTime, MotionEvent.ACTION_UP, center,
-                                LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER);
-
                         base.evaluate();
                     }
                 }
diff --git a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
index 20aa410..a89eab5 100644
--- a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
+++ b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
@@ -49,7 +49,6 @@
 
     @Test
     @NavigationModeSwitch
-    @Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/187761685
     public void testStressPressHome() {
         for (int i = 0; i < STRESS_REPEAT_COUNT; ++i) {
             // Destroy Launcher activity.
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 2be38b0..5531c6e 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -16,11 +16,13 @@
 
 package com.android.quickstep;
 
+import static com.android.launcher3.config.FeatureFlags.ENABLE_CURSOR_HOVER_STATES;
 import static com.android.launcher3.testing.shared.TestProtocol.FLAKY_QUICK_SWITCH_TO_PREVIOUS_APP;
 import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName;
 import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
 import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
 import static com.android.quickstep.TaskbarModeSwitchRule.Mode.PERSISTENT;
+import static com.android.quickstep.TaskbarModeSwitchRule.Mode.TRANSIENT;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -50,6 +52,7 @@
 import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
 import com.android.launcher3.ui.TaplTestsLauncher3;
 import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.TestUtil;
 import com.android.launcher3.util.Wait;
 import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
 import com.android.launcher3.util.rule.TestStabilityRule;
@@ -242,6 +245,19 @@
                 isInState(() -> LauncherState.OVERVIEW));
     }
 
+    @Test
+    @TaskbarModeSwitch(mode = TRANSIENT)
+    public void testSwitchToOverviewWithStashedTaskbar() throws Exception {
+        try (AutoCloseable flag = TestUtil.overrideFlag(ENABLE_CURSOR_HOVER_STATES, true)) {
+            startTestAppsWithCheck();
+            // Set ignoreTaskbarVisibility, as transient taskbar will be stashed after app launch.
+            mLauncher.setIgnoreTaskbarVisibility(true);
+            mLauncher.getLaunchedAppState().switchToOverview();
+        } finally {
+            mLauncher.setIgnoreTaskbarVisibility(false);
+        }
+    }
+
     @Ignore
     @Test
     @NavigationModeSwitch
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsTransientTaskbar.java b/quickstep/tests/src/com/android/quickstep/TaplTestsTransientTaskbar.java
index 3869bf7..b3cec4e 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsTransientTaskbar.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsTransientTaskbar.java
@@ -66,12 +66,10 @@
 
     @Test
     @TaskbarModeSwitch(mode = TRANSIENT)
-    public void testClickHoveredTaskbarToGoHome() {
+    public void testClickHoveredTaskbarToGoHome() throws Exception {
         try (AutoCloseable flag = TestUtil.overrideFlag(ENABLE_CURSOR_HOVER_STATES, true)) {
             getTaskbar().getAppIcon(TEST_APP_NAME).launch(TEST_APP_PACKAGE);
             mLauncher.getLaunchedAppState().clickStashedTaskbarToGoHome();
-        } catch (Exception e) {
-            throw new RuntimeException(e);
         }
     }
 }
diff --git a/quickstep/tests/src/com/android/quickstep/util/SplitSelectDataHolderTest.kt b/quickstep/tests/src/com/android/quickstep/util/SplitSelectDataHolderTest.kt
index fc767fa..c6c5be4 100644
--- a/quickstep/tests/src/com/android/quickstep/util/SplitSelectDataHolderTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/util/SplitSelectDataHolderTest.kt
@@ -381,7 +381,7 @@
     }
 
     @Test
-    fun clearState() {
+    fun clearState_task() {
         splitSelectDataHolder.setInitialTaskSelect(
             sampleTaskInfo,
             STAGE_POSITION_TOP_OR_LEFT,
@@ -392,4 +392,18 @@
         splitSelectDataHolder.resetState()
         assertFalse(splitSelectDataHolder.isSplitSelectActive())
     }
+
+    @Test
+    fun clearState_intent() {
+        splitSelectDataHolder.setInitialTaskSelect(
+                sampleIntent,
+                STAGE_POSITION_TOP_OR_LEFT,
+                sampleItemInfo,
+                null,
+                INVALID_TASK_ID
+        )
+        splitSelectDataHolder.setSecondTask(sampleIntent, sampleUser)
+        splitSelectDataHolder.resetState()
+        assertFalse(splitSelectDataHolder.isSplitSelectActive())
+    }
 }
diff --git a/res/drawable-sw720dp/ic_transient_taskbar_all_apps_search_button.xml b/res/drawable-sw720dp/ic_transient_taskbar_all_apps_search_button.xml
index 22f5d6b..654f0b3 100644
--- a/res/drawable-sw720dp/ic_transient_taskbar_all_apps_search_button.xml
+++ b/res/drawable-sw720dp/ic_transient_taskbar_all_apps_search_button.xml
@@ -19,15 +19,20 @@
     android:viewportWidth="52"
     android:viewportHeight="52">
   <path
-      android:pathData="M42.647,43.855L37.215,38.423C36.784,38.768 36.288,39.041 35.727,39.242C35.167,39.443 34.57,39.544 33.938,39.544C32.372,39.544 31.042,39.005 29.95,37.927C28.872,36.835 28.333,35.505 28.333,33.939C28.333,32.372 28.872,31.05 29.95,29.972C31.042,28.88 32.372,28.334 33.938,28.334C35.505,28.334 36.827,28.88 37.904,29.972C38.997,31.05 39.543,32.372 39.543,33.939C39.543,34.571 39.442,35.167 39.241,35.728C39.04,36.289 38.767,36.784 38.422,37.215L43.854,42.648L42.647,43.855ZM33.938,37.819C35.016,37.819 35.929,37.445 36.676,36.698C37.437,35.936 37.818,35.017 37.818,33.939C37.818,32.861 37.437,31.948 36.676,31.201C35.929,30.439 35.016,30.059 33.938,30.059C32.86,30.059 31.94,30.439 31.179,31.201C30.431,31.948 30.058,32.861 30.058,33.939C30.058,35.017 30.431,35.936 31.179,36.698C31.94,37.445 32.86,37.819 33.938,37.819Z"
-      android:fillColor="#48473A"/>
+      android:pathData="M39.4,17.5c0,3.1 -2.5,5.5 -5.5,5.5c-3.1,0 -5.5,-2.5 -5.5,-5.5s2.5,-5.5 5.5,-5.5C36.9,12 39.4,14.5 39.4,17.5z"
+      android:fillColor="#FF000000"/>
   <path
-      android:pathData="M39.42,17.543C39.42,20.605 36.938,23.086 33.876,23.086C30.815,23.086 28.333,20.605 28.333,17.543C28.333,14.482 30.815,12 33.876,12C36.938,12 39.42,14.482 39.42,17.543Z"
-      android:fillColor="#48473A"/>
+      android:pathData="M23.1,17.5c0,3.1 -2.5,5.5 -5.5,5.5S12,20.6 12,17.5s2.5,-5.5 5.5,-5.5S23.1,14.5 23.1,17.5z"
+      android:fillColor="#FF000000"/>
   <path
-      android:pathData="M23.086,17.543C23.086,20.605 20.605,23.086 17.543,23.086C14.482,23.086 12,20.605 12,17.543C12,14.482 14.482,12 17.543,12C20.605,12 23.086,14.482 23.086,17.543Z"
-      android:fillColor="#48473A"/>
+      android:pathData="M17.5,33.9m-5.5,0a5.5,5.5 0,1 1,11 0a5.5,5.5 0,1 1,-11 0"
+      android:fillColor="#FF000000"/>
   <path
-      android:pathData="M17.543,33.877m-5.543,0a5.543,5.543 0,1 1,11.086 0a5.543,5.543 0,1 1,-11.086 0"
-      android:fillColor="#48473A"/>
+      android:fillColor="#FF000000"
+      android:pathData="M33.9,30c2.1,0 3.9,1.7 3.9,3.9s-1.7,3.9 -3.9,3.9S30,36.1 30,33.9S31.8,30 33.9,30M33.9,28.3c-3.1,0 -5.6,2.5 -5.6,5.6c0,3.1 2.5,5.6 5.6,5.6c3.1,0 5.6,-2.5 5.6,-5.6C39.5,30.8 37,28.3 33.9,28.3L33.9,28.3z"/>
+  <path
+      android:pathData="M36.9,37L43.2,43.3"
+      android:strokeWidth="1.7"
+      android:fillColor="#00000000"
+      android:strokeColor="#000000"/>
 </vector>
diff --git a/res/drawable/ic_taskbar_all_apps_search_button.xml b/res/drawable/ic_taskbar_all_apps_search_button.xml
index 8a42dc7..8fbe539 100644
--- a/res/drawable/ic_taskbar_all_apps_search_button.xml
+++ b/res/drawable/ic_taskbar_all_apps_search_button.xml
@@ -19,15 +19,20 @@
     android:viewportWidth="44"
     android:viewportHeight="44">
   <path
-      android:pathData="M36.938,38L32.164,33.225C31.785,33.528 31.349,33.769 30.856,33.945C30.363,34.122 29.839,34.211 29.283,34.211C27.906,34.211 26.738,33.737 25.778,32.79C24.83,31.83 24.357,30.661 24.357,29.284C24.357,27.907 24.83,26.745 25.778,25.798C26.738,24.837 27.906,24.357 29.283,24.357C30.66,24.357 31.822,24.837 32.77,25.798C33.73,26.745 34.21,27.907 34.21,29.284C34.21,29.84 34.121,30.364 33.945,30.857C33.768,31.349 33.528,31.785 33.225,32.164L38,36.939L36.938,38ZM29.283,32.695C30.231,32.695 31.033,32.366 31.69,31.709C32.359,31.04 32.694,30.232 32.694,29.284C32.694,28.337 32.359,27.535 31.69,26.878C31.033,26.208 30.231,25.873 29.283,25.873C28.336,25.873 27.527,26.208 26.858,26.878C26.201,27.535 25.873,28.337 25.873,29.284C25.873,30.232 26.201,31.04 26.858,31.709C27.527,32.366 28.336,32.695 29.283,32.695Z"
-      android:fillColor="#52443C"/>
+      android:fillColor="#FF000000"
+      android:pathData="M34.1,14.9c0,2.7 -2.2,4.9 -4.9,4.9s-4.9,-2.2 -4.9,-4.9s2.2,-4.9 4.9,-4.9S34.1,12.2 34.1,14.9z"/>
   <path
-      android:pathData="M34.102,14.873C34.102,17.563 31.92,19.745 29.229,19.745C26.538,19.745 24.357,17.563 24.357,14.873C24.357,12.182 26.538,10 29.229,10C31.92,10 34.102,12.182 34.102,14.873Z"
-      android:fillColor="#52443C"/>
+      android:fillColor="#FF000000"
+      android:pathData="M19.7,14.9c0,2.7 -2.2,4.9 -4.9,4.9S10,17.6 10,14.9s2.2,-4.9 4.9,-4.9S19.7,12.2 19.7,14.9z"/>
   <path
-      android:pathData="M19.745,14.873C19.745,17.563 17.563,19.745 14.873,19.745C12.182,19.745 10,17.563 10,14.873C10,12.182 12.182,10 14.873,10C17.563,10 19.745,12.182 19.745,14.873Z"
-      android:fillColor="#52443C"/>
+      android:fillColor="#FF000000"
+      android:pathData="M14.9,29.2m-4.9,0a4.9,4.9 0,1 1,9.8 0a4.9,4.9 0,1 1,-9.8 0"/>
   <path
-      android:pathData="M14.873,29.23m-4.872,0a4.872,4.872 0,1 1,9.745 0a4.872,4.872 0,1 1,-9.745 0"
-      android:fillColor="#52443C"/>
+      android:fillColor="#FF000000"
+      android:pathData="M29.3,25.9c1.9,0 3.4,1.5 3.4,3.4c0,1.9 -1.5,3.4 -3.4,3.4s-3.4,-1.5 -3.4,-3.4C25.9,27.4 27.4,25.9 29.3,25.9M29.3,24.4c-2.7,0 -4.9,2.2 -4.9,4.9s2.2,4.9 4.9,4.9c2.7,0 4.9,-2.2 4.9,-4.9S32,24.4 29.3,24.4L29.3,24.4z"/>
+  <path
+      android:pathData="M31.9,32L37.4,37.5"
+      android:strokeWidth="1.5"
+      android:fillColor="#00000000"
+      android:strokeColor="#000000"/>
 </vector>
diff --git a/res/drawable/ic_transient_taskbar_all_apps_search_button.xml b/res/drawable/ic_transient_taskbar_all_apps_search_button.xml
index 52d6818..59a666b 100644
--- a/res/drawable/ic_transient_taskbar_all_apps_search_button.xml
+++ b/res/drawable/ic_transient_taskbar_all_apps_search_button.xml
@@ -19,15 +19,20 @@
     android:viewportWidth="48"
     android:viewportHeight="48">
   <path
-      android:pathData="M40.647,41.855L35.215,36.423C34.784,36.768 34.288,37.041 33.727,37.242C33.167,37.443 32.57,37.544 31.938,37.544C30.372,37.544 29.042,37.005 27.95,35.927C26.872,34.835 26.333,33.505 26.333,31.939C26.333,30.372 26.872,29.05 27.95,27.972C29.042,26.88 30.372,26.334 31.938,26.334C33.505,26.334 34.827,26.88 35.904,27.972C36.997,29.05 37.543,30.372 37.543,31.939C37.543,32.571 37.442,33.167 37.241,33.728C37.04,34.289 36.767,34.784 36.422,35.215L41.854,40.648L40.647,41.855ZM31.938,35.819C33.016,35.819 33.929,35.445 34.676,34.698C35.437,33.936 35.818,33.017 35.818,31.939C35.818,30.861 35.437,29.948 34.676,29.201C33.929,28.439 33.016,28.059 31.938,28.059C30.86,28.059 29.94,28.439 29.179,29.201C28.431,29.948 28.058,30.861 28.058,31.939C28.058,33.017 28.431,33.936 29.179,34.698C29.94,35.445 30.86,35.819 31.938,35.819Z"
-      android:fillColor="#52443C"/>
+      android:fillColor="#FF000000"
+      android:pathData="M37.4,15.5c0,3.1 -2.5,5.5 -5.5,5.5c-3.1,0 -5.5,-2.5 -5.5,-5.5s2.5,-5.5 5.5,-5.5C34.9,10 37.4,12.5 37.4,15.5z"/>
   <path
-      android:pathData="M37.42,15.543C37.42,18.605 34.938,21.086 31.876,21.086C28.815,21.086 26.333,18.605 26.333,15.543C26.333,12.482 28.815,10 31.876,10C34.938,10 37.42,12.482 37.42,15.543Z"
-      android:fillColor="#52443C"/>
+      android:fillColor="#FF000000"
+      android:pathData="M21.1,15.5c0,3.1 -2.5,5.5 -5.5,5.5S10,18.6 10,15.5s2.5,-5.5 5.5,-5.5S21.1,12.5 21.1,15.5z"/>
   <path
-      android:pathData="M21.086,15.543C21.086,18.605 18.605,21.086 15.543,21.086C12.482,21.086 10,18.605 10,15.543C10,12.482 12.482,10 15.543,10C18.605,10 21.086,12.482 21.086,15.543Z"
-      android:fillColor="#52443C"/>
+      android:fillColor="#FF000000"
+      android:pathData="M15.5,31.9m-5.5,0a5.5,5.5 0,1 1,11 0a5.5,5.5 0,1 1,-11 0"/>
   <path
-      android:pathData="M15.543,31.877m-5.543,0a5.543,5.543 0,1 1,11.086 0a5.543,5.543 0,1 1,-11.086 0"
-      android:fillColor="#52443C"/>
+      android:fillColor="#FF000000"
+      android:pathData="M31.9,28c2.1,0 3.9,1.7 3.9,3.9s-1.7,3.9 -3.9,3.9c-2.1,0 -3.9,-1.7 -3.9,-3.9S29.8,28 31.9,28M31.9,26.3c-3.1,0 -5.6,2.5 -5.6,5.6c0,3.1 2.5,5.6 5.6,5.6c3.1,0 5.6,-2.5 5.6,-5.6C37.5,28.8 35,26.3 31.9,26.3L31.9,26.3z"/>
+  <path
+      android:pathData="M34.9,35L41.2,41.3"
+      android:strokeWidth="1.7"
+      android:fillColor="#00000000"
+      android:strokeColor="#000000"/>
 </vector>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index ee7805f..333fded 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -35,7 +35,7 @@
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d lai ja %2$d kõrge"</string>
     <string name="widget_preview_context_description" msgid="9045841361655787574">"Vidin <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
-    <string name="add_item_request_drag_hint" msgid="8730547755622776606">"Vidina teisaldamiseks avakuval puudutage vidinat ja hoidke seda all"</string>
+    <string name="add_item_request_drag_hint" msgid="8730547755622776606">"Vidina teisaldamiseks avakuval puudutage vidinat pikalt"</string>
     <string name="add_to_home_screen" msgid="9168649446635919791">"Lisa avakuvale"</string>
     <string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Vidin <xliff:g id="WIDGET_NAME">%1$s</xliff:g> lisati avakuvale"</string>
     <string name="suggested_widgets_header_title" msgid="1844314680798145222">"Soovitused"</string>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index 72ab9b2..a35a70b 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -61,7 +61,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" ಹೊಂದಿಕೆಯ ಯಾವುದೇ ಅಪ್ಲಿಕೇಶನ್‌ಗಳು ಕಂಡುಬಂದಿಲ್ಲ"</string>
     <string name="label_application" msgid="8531721983832654978">"ಆ್ಯಪ್"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"ಎಲ್ಲಾ ಆ್ಯಪ್‌ಗಳು"</string>
-    <string name="notifications_header" msgid="1404149926117359025">"ಅಧಿಸೂಚನೆಗಳು"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"ನೋಟಿಫಿಕೇಶನ್‌ಗಳು"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"ಶಾರ್ಟ್‌ಕಟ್ ಸರಿಸಲು ಸ್ಪರ್ಶಿಸಿ ಮತ್ತು ಹಿಡಿದುಕೊಳ್ಳಿ."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"ಶಾರ್ಟ್‌ಕಟ್ ಸರಿಸಲು ಅಥವಾ ಕಸ್ಟಮ್ ಕ್ರಿಯೆಗಳನ್ನು ಬಳಸಲು ಡಬಲ್-ಟ್ಯಾಪ್ ಮಾಡಿ ಮತ್ತು ಹಿಡಿದುಕೊಳ್ಳಿ."</string>
     <string name="out_of_space" msgid="6455557115204099579">"ಈ ಹೋಮ್ ಸ್ಕ್ರೀನ್‌ನಲ್ಲಿ ಸ್ಥಳಾವಕಾಶವಿಲ್ಲ"</string>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index 54cc691..c73295d 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -35,7 +35,7 @@
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d रूंद बाय %2$d उंच"</string>
     <string name="widget_preview_context_description" msgid="9045841361655787574">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> विजेट"</string>
-    <string name="add_item_request_drag_hint" msgid="8730547755622776606">"होम स्क्रीनवर ते हलवण्यासाठी विजेटला स्पर्श करा आणि धरून ठेवा"</string>
+    <string name="add_item_request_drag_hint" msgid="8730547755622776606">"होम स्क्रीनवर हलवण्यासाठी विजेटला स्पर्श करून धरून ठेवा"</string>
     <string name="add_to_home_screen" msgid="9168649446635919791">"होम स्क्रीनवर जोडा"</string>
     <string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> हे विजेट तुमच्या होम स्क्रीनवर जोडले आहे"</string>
     <string name="suggested_widgets_header_title" msgid="1844314680798145222">"सूचना"</string>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index 3cc6bff..0f0960e 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -104,7 +104,7 @@
     <string name="settings_button_text" msgid="8873672322605444408">"主畫面設定"</string>
     <string name="msg_disabled_by_admin" msgid="6898038085516271325">"已由你的管理員停用"</string>
     <string name="allow_rotation_title" msgid="7222049633713050106">"允許旋轉主畫面"</string>
-    <string name="allow_rotation_desc" msgid="8662546029078692509">"當手機旋轉時"</string>
+    <string name="allow_rotation_desc" msgid="8662546029078692509">"隨手機旋轉"</string>
     <string name="notification_dots_title" msgid="9062440428204120317">"通知圓點"</string>
     <string name="notification_dots_desc_on" msgid="1679848116452218908">"開啟"</string>
     <string name="notification_dots_desc_off" msgid="1760796511504341095">"關閉"</string>
diff --git a/res/values/config.xml b/res/values/config.xml
index 8f9731c..cd9c9de 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -241,4 +241,7 @@
         <item>@dimen/iconSize66dp</item>
         <item>@dimen/iconSize72dp</item>
     </integer-array>
+
+    <!--  Used for custom widgets  -->
+    <array name="custom_widget_providers"/>
 </resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 1079e00..c6fce28 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -385,6 +385,9 @@
     <dimen name="taskbar_button_margin_split">0dp</dimen>
     <dimen name="taskbar_button_margin_6_5">0dp</dimen>
 
+    <!-- Bubble bar (placeholders to compile in Launcher3 without Quickstep) -->
+    <dimen name="bubblebar_hotseat_adjustment_threshold">0dp</dimen>
+
     <!-- Size of the maximum radius for the enforced rounded rectangles. -->
     <dimen name="enforced_rounded_corner_max_radius">16dp</dimen>
 
diff --git a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
index 55b8fcc..ec874b9 100644
--- a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
+++ b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
@@ -1,12 +1,28 @@
 package com.android.launcher3;
 
+import static android.os.Process.myUserHandle;
+
+import android.appwidget.AppWidgetHost;
 import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.database.Cursor;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.WorkerThread;
+
+import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.model.LoaderTask;
+import com.android.launcher3.model.ModelDbController;
+import com.android.launcher3.model.WidgetsModel;
+import com.android.launcher3.model.data.LauncherAppWidgetInfo;
+import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.provider.RestoreDbTask;
+import com.android.launcher3.util.ContentWriter;
+import com.android.launcher3.util.IntArray;
 import com.android.launcher3.widget.LauncherWidgetHolder;
 
 public class AppWidgetsRestoredReceiver extends BroadcastReceiver {
@@ -31,4 +47,131 @@
             }
         }
     }
-}
\ No newline at end of file
+
+    /**
+     * Updates the app widgets whose id has changed during the restore process.
+     */
+    @WorkerThread
+    public static void restoreAppWidgetIds(Context context, ModelDbController controller,
+            int[] oldWidgetIds, int[] newWidgetIds, @NonNull AppWidgetHost host) {
+        if (WidgetsModel.GO_DISABLE_WIDGETS) {
+            Log.e(TAG, "Skipping widget ID remap as widgets not supported");
+            host.deleteHost();
+            return;
+        }
+        if (!RestoreDbTask.isPending(context)) {
+            // Someone has already gone through our DB once, probably LoaderTask. Skip any further
+            // modifications of the DB.
+            Log.e(TAG, "Skipping widget ID remap as DB already in use");
+            for (int widgetId : newWidgetIds) {
+                Log.d(TAG, "Deleting widgetId: " + widgetId);
+                host.deleteAppWidgetId(widgetId);
+            }
+            return;
+        }
+
+        final AppWidgetManager widgets = AppWidgetManager.getInstance(context);
+
+        Log.d(TAG, "restoreAppWidgetIds: "
+                + "oldWidgetIds=" + IntArray.wrap(oldWidgetIds).toConcatString()
+                + ", newWidgetIds=" + IntArray.wrap(newWidgetIds).toConcatString());
+
+        // TODO(b/234700507): Remove the logs after the bug is fixed
+        logDatabaseWidgetInfo(controller);
+
+        for (int i = 0; i < oldWidgetIds.length; i++) {
+            Log.i(TAG, "Widget state restore id " + oldWidgetIds[i] + " => " + newWidgetIds[i]);
+
+            final AppWidgetProviderInfo provider = widgets.getAppWidgetInfo(newWidgetIds[i]);
+            final int state;
+            if (LoaderTask.isValidProvider(provider)) {
+                // This will ensure that we show 'Click to setup' UI if required.
+                state = LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
+            } else {
+                state = LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
+            }
+
+            // b/135926478: Work profile widget restore is broken in platform. This forces us to
+            // recreate the widget during loading with the correct host provider.
+            long mainProfileId = UserCache.INSTANCE.get(context)
+                    .getSerialNumberForUser(myUserHandle());
+            long controllerProfileId = controller.getSerialNumberForUser(myUserHandle());
+            String oldWidgetId = Integer.toString(oldWidgetIds[i]);
+            final String where = "appWidgetId=? and (restored & 1) = 1 and profileId=?";
+            String profileId = Long.toString(mainProfileId);
+            final String[] args = new String[] { oldWidgetId, profileId };
+            Log.d(TAG, "restoreAppWidgetIds: querying profile id=" + profileId
+                    + " with controller profile ID=" + controllerProfileId);
+            int result = new ContentWriter(context,
+                            new ContentWriter.CommitParams(controller, where, args))
+                    .put(LauncherSettings.Favorites.APPWIDGET_ID, newWidgetIds[i])
+                    .put(LauncherSettings.Favorites.RESTORED, state)
+                    .commit();
+            if (result == 0) {
+                // TODO(b/234700507): Remove the logs after the bug is fixed
+                Log.e(TAG, "restoreAppWidgetIds: remapping failed since the widget is not in"
+                        + " the database anymore");
+                try (Cursor cursor = controller.getDb().query(
+                        Favorites.TABLE_NAME,
+                        new String[]{Favorites.APPWIDGET_ID},
+                        "appWidgetId=?", new String[]{oldWidgetId}, null, null, null)) {
+                    if (!cursor.moveToFirst()) {
+                        // The widget no long exists.
+                        Log.d(TAG, "Deleting widgetId: " + newWidgetIds[i] + " with old id: "
+                                + oldWidgetId);
+                        host.deleteAppWidgetId(newWidgetIds[i]);
+                    }
+                }
+            }
+        }
+
+        LauncherAppState app = LauncherAppState.getInstanceNoCreate();
+        if (app != null) {
+            app.getModel().forceReload();
+        }
+    }
+
+    private static void logDatabaseWidgetInfo(ModelDbController controller) {
+        try (Cursor cursor = controller.getDb().query(Favorites.TABLE_NAME,
+                new String[]{Favorites.APPWIDGET_ID, Favorites.RESTORED, Favorites.PROFILE_ID},
+                Favorites.APPWIDGET_ID + "!=" + LauncherAppWidgetInfo.NO_ID, null,
+                null, null, null)) {
+            IntArray widgetIdList = new IntArray();
+            IntArray widgetRestoreList = new IntArray();
+            IntArray widgetProfileIdList = new IntArray();
+
+            if (cursor.moveToFirst()) {
+                final int widgetIdColumnIndex = cursor.getColumnIndex(Favorites.APPWIDGET_ID);
+                final int widgetRestoredColumnIndex = cursor.getColumnIndex(Favorites.RESTORED);
+                final int widgetProfileIdIndex = cursor.getColumnIndex(Favorites.PROFILE_ID);
+                while (!cursor.isAfterLast()) {
+                    int widgetId = cursor.getInt(widgetIdColumnIndex);
+                    int widgetRestoredFlag = cursor.getInt(widgetRestoredColumnIndex);
+                    int widgetProfileId = cursor.getInt(widgetProfileIdIndex);
+
+                    widgetIdList.add(widgetId);
+                    widgetRestoreList.add(widgetRestoredFlag);
+                    widgetProfileIdList.add(widgetProfileId);
+                    cursor.moveToNext();
+                }
+            }
+
+            StringBuilder builder = new StringBuilder();
+            builder.append("[");
+            for (int i = 0; i < widgetIdList.size(); i++) {
+                builder.append("[")
+                        .append(widgetIdList.get(i))
+                        .append(", ")
+                        .append(widgetRestoreList.get(i))
+                        .append(", ")
+                        .append(widgetProfileIdList.get(i))
+                        .append("]");
+            }
+            builder.append("]");
+            Log.d(TAG, "restoreAppWidgetIds: all widget ids in database: "
+                    + builder.toString());
+        } catch (Exception ex) {
+            Log.e(TAG, "Getting widget ids from the database failed", ex);
+        }
+    }
+}
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 347c7af..9b57799 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -397,8 +397,8 @@
     }
 
     protected boolean shouldUseTheme() {
-        return mDisplay == DISPLAY_WORKSPACE || mDisplay == DISPLAY_FOLDER
-                || mDisplay == DISPLAY_TASKBAR;
+        return (mDisplay == DISPLAY_WORKSPACE || mDisplay == DISPLAY_FOLDER
+                || mDisplay == DISPLAY_TASKBAR) && Themes.isThemedIconEnabled(getContext());
     }
 
     /**
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index e7d1db4..c05afc9 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -219,6 +219,9 @@
     private final int mMinHotseatQsbWidthPx;
     private final int mMaxHotseatIconSpacePx;
     public final int inlineNavButtonsEndSpacingPx;
+    // Space required for the bubble bar between the hotseat and the edge of the screen. If there's
+    // not enough space, the hotseat will adjust itself for the bubble bar.
+    private final int mBubbleBarSpaceThresholdPx;
 
     // Bottom sheets
     public int bottomSheetTopPadding;
@@ -365,7 +368,10 @@
         }
 
         isTransientTaskbar = DisplayController.isTransientTaskbar(context);
-        if (isTransientTaskbar) {
+        if (!isTaskbarPresent) {
+            taskbarIconSize = taskbarHeight = stashedTaskbarHeight = taskbarBottomMargin = 0;
+            startAlignTaskbar = false;
+        } else if (isTransientTaskbar) {
             float invTransientIconSizeDp = inv.transientTaskbarIconSize[mTypeIndex];
             taskbarIconSize = pxFromDp(invTransientIconSizeDp, mMetrics);
             taskbarHeight = Math.round((taskbarIconSize * ICON_VISIBLE_AREA_FACTOR)
@@ -567,6 +573,9 @@
             hotseatBarEndOffset = 0;
         }
 
+        mBubbleBarSpaceThresholdPx =
+                res.getDimensionPixelSize(R.dimen.bubblebar_hotseat_adjustment_threshold);
+
         // Needs to be calculated after hotseatBarSizePx is correct,
         // for the available height to be correct
         if (mIsResponsiveGrid) {
@@ -1555,6 +1564,32 @@
         paddings.bottom -= insets.bottom;
     }
 
+
+    /**
+     * Returns the new border space that should be used between hotseat icons after adjusting it to
+     * the bubble bar.
+     *
+     * <p>If there's no adjustment needed, this method returns {@code 0}.
+     */
+    public float getHotseatAdjustedBorderSpaceForBubbleBar(Context context) {
+        // only need to adjust when QSB is on top of the hotseat.
+        if (isQsbInline) {
+            return 0;
+        }
+
+        // no need to adjust if there's enough space for the bubble bar to the right of the hotseat.
+        if (getHotseatLayoutPadding(context).right > mBubbleBarSpaceThresholdPx) {
+            return 0;
+        }
+
+        // The adjustment is shrinking the hotseat's width by 1 icon on either side.
+        int iconsWidth =
+                iconSizePx * numShownHotseatIcons + hotseatBorderSpace * (numShownHotseatIcons - 1);
+        int newWidth = iconsWidth - 2 * iconSizePx;
+        // Evenly space the icons within the boundaries of the new width.
+        return (float) (newWidth - iconSizePx * numShownHotseatIcons) / (numShownHotseatIcons - 1);
+    }
+
     /**
      * Returns the padding for hotseat view
      */
@@ -1715,7 +1750,7 @@
 
     /** Gets the space that the overview actions will take, including bottom margin. */
     public int getOverviewActionsClaimedSpace() {
-        int overviewActionsSpace = isTablet && FeatureFlags.ENABLE_GRID_ONLY_OVERVIEW.get()
+        int overviewActionsSpace = isTablet && FeatureFlags.enableGridOnlyOverview()
                 ? 0
                 : (overviewActionsTopMarginPx + overviewActionsHeight);
         return overviewActionsSpace + getOverviewActionsClaimedSpaceBelow();
@@ -2162,5 +2197,4 @@
                     mIsGestureMode, mViewScaleProvider, mOverrideProvider);
         }
     }
-
 }
diff --git a/src/com/android/launcher3/FastScrollRecyclerView.java b/src/com/android/launcher3/FastScrollRecyclerView.java
index c16b319..67ed55d 100644
--- a/src/com/android/launcher3/FastScrollRecyclerView.java
+++ b/src/com/android/launcher3/FastScrollRecyclerView.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3;
 
+import static com.android.launcher3.testing.shared.TestProtocol.SCROLL_FINISHED_MESSAGE;
+
 import android.content.Context;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
@@ -170,7 +172,8 @@
         super.onScrollStateChanged(state);
 
         if (state == SCROLL_STATE_IDLE) {
-            AccessibilityManagerCompat.sendScrollFinishedEventToTest(getContext());
+            AccessibilityManagerCompat.sendTestProtocolEventToTest(getContext(),
+                    SCROLL_FINISHED_MESSAGE);
         }
     }
 
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index 03afba1..3b12b86 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -16,6 +16,12 @@
 
 package com.android.launcher3;
 
+import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
+import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_BUBBLE_ADJUSTMENT_ANIM;
+
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
 import android.content.Context;
 import android.graphics.Rect;
 import android.util.AttributeSet;
@@ -27,6 +33,10 @@
 import android.view.ViewGroup;
 import android.widget.FrameLayout;
 
+import com.android.launcher3.util.HorizontalInsettableView;
+import com.android.launcher3.util.MultiTranslateDelegate;
+import com.android.launcher3.views.ActivityContext;
+
 /**
  * View class that represents the bottom row of the home screen.
  */
@@ -34,6 +44,7 @@
 
     // Ratio of empty space, qsb should take up to appear visually centered.
     public static final float QSB_CENTER_FACTOR = .325f;
+    private static final int BUBBLE_BAR_ADJUSTMENT_ANIMATION_DURATION_MS = 250;
 
     @ViewDebug.ExportedProperty(category = "launcher")
     private boolean mHasVerticalHotseat;
@@ -72,9 +83,36 @@
     }
 
     public void resetLayout(boolean hasVerticalHotseat) {
+        ActivityContext activityContext = ActivityContext.lookupContext(getContext());
+        boolean bubbleBarEnabled = activityContext.isBubbleBarEnabled();
+        boolean hasBubbles = activityContext.hasBubbles();
         removeAllViewsInLayout();
         mHasVerticalHotseat = hasVerticalHotseat;
         DeviceProfile dp = mActivity.getDeviceProfile();
+
+        if (bubbleBarEnabled) {
+            float adjustedBorderSpace = dp.getHotseatAdjustedBorderSpaceForBubbleBar(getContext());
+            if (hasBubbles && Float.compare(adjustedBorderSpace, 0f) != 0) {
+                getShortcutsAndWidgets().setTranslationProvider(child -> {
+                    int index = getShortcutsAndWidgets().indexOfChild(child);
+                    float borderSpaceDelta = adjustedBorderSpace - dp.hotseatBorderSpace;
+                    return dp.iconSizePx + index * borderSpaceDelta;
+                });
+                if (mQsb instanceof HorizontalInsettableView) {
+                    HorizontalInsettableView insettableQsb = (HorizontalInsettableView) mQsb;
+                    final float insetFraction = (float) dp.iconSizePx / dp.hotseatQsbWidth;
+                    // post this to the looper so that QSB has a chance to redraw itself, e.g.
+                    // after device rotation
+                    mQsb.post(() -> insettableQsb.setHorizontalInsets(insetFraction));
+                }
+            } else {
+                getShortcutsAndWidgets().setTranslationProvider(null);
+                if (mQsb instanceof HorizontalInsettableView) {
+                    ((HorizontalInsettableView) mQsb).setHorizontalInsets(0);
+                }
+            }
+        }
+
         resetCellSize(dp);
         if (hasVerticalHotseat) {
             setGridSize(1, dp.numShownHotseatIcons);
@@ -83,6 +121,62 @@
         }
     }
 
+    /**
+     * Adjust the hotseat icons for the bubble bar.
+     *
+     * <p>When the bubble bar becomes visible, if needed, this method animates the hotseat icons
+     * to reduce the spacing between them and make room for the bubble bar. The QSB width is
+     * animated as well to align with the hotseat icons.
+     *
+     * <p>When the bubble bar goes away, any adjustments that were previously made are reversed.
+     */
+    public void adjustForBubbleBar(boolean isBubbleBarVisible) {
+        DeviceProfile dp = mActivity.getDeviceProfile();
+        float adjustedBorderSpace = dp.getHotseatAdjustedBorderSpaceForBubbleBar(getContext());
+        if (Float.compare(adjustedBorderSpace, 0f) == 0) {
+            return;
+        }
+
+        ShortcutAndWidgetContainer icons = getShortcutsAndWidgets();
+        AnimatorSet animatorSet = new AnimatorSet();
+        float borderSpaceDelta = adjustedBorderSpace - dp.hotseatBorderSpace;
+
+        // update the translation provider for future layout passes of hotseat icons.
+        if (isBubbleBarVisible) {
+            icons.setTranslationProvider(child -> {
+                int index = icons.indexOfChild(child);
+                return dp.iconSizePx + index * borderSpaceDelta;
+            });
+        } else {
+            icons.setTranslationProvider(null);
+        }
+
+        for (int i = 0; i < icons.getChildCount(); i++) {
+            View child = icons.getChildAt(i);
+            float tx = isBubbleBarVisible ? dp.iconSizePx + i * borderSpaceDelta : 0;
+            if (child instanceof Reorderable) {
+                MultiTranslateDelegate mtd = ((Reorderable) child).getTranslateDelegate();
+                animatorSet.play(
+                        mtd.getTranslationX(INDEX_BUBBLE_ADJUSTMENT_ANIM).animateToValue(tx));
+            } else {
+                animatorSet.play(ObjectAnimator.ofFloat(child, VIEW_TRANSLATE_X, tx));
+            }
+        }
+        if (mQsb instanceof HorizontalInsettableView) {
+            HorizontalInsettableView horizontalInsettableQsb = (HorizontalInsettableView) mQsb;
+            ValueAnimator qsbAnimator = ValueAnimator.ofFloat(0f, 1f);
+            qsbAnimator.addUpdateListener(animation -> {
+                float fraction = qsbAnimator.getAnimatedFraction();
+                float insetFraction = isBubbleBarVisible
+                        ? (float) dp.iconSizePx * fraction / dp.hotseatQsbWidth
+                        : (float) dp.iconSizePx * (1 - fraction) / dp.hotseatQsbWidth;
+                horizontalInsettableQsb.setHorizontalInsets(insetFraction);
+            });
+            animatorSet.play(qsbAnimator);
+        }
+        animatorSet.setDuration(BUBBLE_BAR_ADJUSTMENT_ANIMATION_DURATION_MS).start();
+    }
+
     @Override
     public void setInsets(Rect insets) {
         FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index e3de79e..c619bc5 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -18,7 +18,6 @@
 
 import static com.android.launcher3.LauncherPrefs.GRID_NAME;
 import static com.android.launcher3.Utilities.dpiFromPx;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_TWO_PANEL_HOME;
 import static com.android.launcher3.testing.shared.ResourceUtils.INVALID_RESOURCE_HANDLE;
 import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY;
 import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE;
@@ -316,7 +315,7 @@
         int type = displayInfo.supportedBounds.stream()
                 .mapToInt(bounds -> displayInfo.isTablet(bounds) ? flagTablet : flagPhone)
                 .reduce(0, (a, b) -> a | b);
-        if ((type == (flagPhone | flagTablet)) && ENABLE_TWO_PANEL_HOME.get()) {
+        if (type == (flagPhone | flagTablet)) {
             // device has profiles supporting both phone and table modes
             return TYPE_MULTI_DISPLAY;
         } else if (type == flagTablet) {
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 606b2c4..b8e7737 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -68,6 +68,7 @@
 import static com.android.launcher3.logging.StatsLogManager.StatsLatencyLogger.LatencyType.WARM;
 import static com.android.launcher3.model.ItemInstallQueue.FLAG_ACTIVITY_PAUSED;
 import static com.android.launcher3.model.ItemInstallQueue.FLAG_DRAG_AND_DROP;
+import static com.android.launcher3.model.data.LauncherAppWidgetInfo.CUSTOM_WIDGET_ID;
 import static com.android.launcher3.popup.SystemShortcut.APP_INFO;
 import static com.android.launcher3.popup.SystemShortcut.INSTALL;
 import static com.android.launcher3.popup.SystemShortcut.WIDGETS;
@@ -144,6 +145,7 @@
 import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.allapps.BaseSearchConfig;
 import com.android.launcher3.allapps.DiscoveryBounce;
+import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.PropertyListBuilder;
 import com.android.launcher3.apppairs.AppPairIcon;
 import com.android.launcher3.celllayout.CellPosMapper;
@@ -885,7 +887,7 @@
                 if (widgetInfo != null) {
                     // Since the view was just bound, also launch the configure activity if needed
                     LauncherAppWidgetProviderInfo provider = mAppWidgetManager
-                            .getLauncherAppWidgetInfo(widgetId);
+                            .getLauncherAppWidgetInfo(widgetId, info.getTargetComponent());
                     if (provider != null) {
                         new WidgetAddFlowHandler(provider)
                                 .startConfigActivity(this, widgetInfo,
@@ -1499,7 +1501,8 @@
             AppWidgetHostView hostView, LauncherAppWidgetProviderInfo appWidgetInfo) {
 
         if (appWidgetInfo == null) {
-            appWidgetInfo = mAppWidgetManager.getLauncherAppWidgetInfo(appWidgetId);
+            appWidgetInfo = mAppWidgetManager.getLauncherAppWidgetInfo(appWidgetId,
+                    itemInfo.getTargetComponent());
         }
 
         if (hostView == null) {
@@ -1727,7 +1730,16 @@
         if (getStateManager().isInStableState(ALL_APPS)) {
             getStateManager().goToState(NORMAL, alreadyOnHome);
         } else {
-            showAllAppsFromIntent(alreadyOnHome);
+            AbstractFloatingView.closeAllOpenViews(this);
+            getStateManager().goToState(ALL_APPS, true /* animated */,
+                    new AnimationSuccessListener() {
+                        @Override
+                        public void onAnimationSuccess(Animator animator) {
+                            if (mAppsView.getSearchUiManager().getEditText() != null) {
+                                mAppsView.getSearchUiManager().getEditText().requestFocus();
+                            }
+                        }
+                    });
         }
     }
 
@@ -1985,8 +1997,8 @@
             // In this case, we either need to start an activity to get permission to bind
             // the widget, or we need to start an activity to configure the widget, or both.
             if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET) {
-                appWidgetId = CustomWidgetManager.INSTANCE.get(this).getWidgetIdForCustomProvider(
-                        info.componentName);
+                appWidgetId = CustomWidgetManager.INSTANCE.get(this)
+                        .allocateCustomAppWidgetId(info.componentName);
             } else {
                 appWidgetId = getAppWidgetHolder().allocateAppWidgetId();
             }
@@ -2313,6 +2325,7 @@
      * <p>
      * Implementation of the method from LauncherModel.Callbacks.
      */
+    @Override
     public void startBinding() {
         TraceHelper.INSTANCE.beginSection("startBinding");
         // Floating panels (except the full widget sheet) are associated with individual icons. If
@@ -2632,9 +2645,10 @@
                     }
                 }
             } else {
-                appWidgetInfo = mAppWidgetManager.getLauncherAppWidgetInfo(item.appWidgetId);
+                appWidgetInfo = mAppWidgetManager.getLauncherAppWidgetInfo(item.appWidgetId,
+                        item.getTargetComponent());
                 if (appWidgetInfo == null) {
-                    if (item.appWidgetId <= LauncherAppWidgetInfo.CUSTOM_WIDGET_ID) {
+                    if (item.appWidgetId <= CUSTOM_WIDGET_ID) {
                         removalReason =
                                 "CustomWidgetManager cannot find provider from that widget id.";
                     } else {
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 4b4a4a5..5ce88a3 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -19,6 +19,7 @@
 import static com.android.app.animation.Interpolators.SCROLL;
 import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled;
 import static com.android.launcher3.compat.AccessibilityManagerCompat.isObservedEventType;
+import static com.android.launcher3.testing.shared.TestProtocol.SCROLL_FINISHED_MESSAGE;
 import static com.android.launcher3.touch.OverScroll.OVERSCROLL_DAMP_FACTOR;
 import static com.android.launcher3.touch.PagedOrientationHandler.VIEW_SCROLL_BY;
 import static com.android.launcher3.touch.PagedOrientationHandler.VIEW_SCROLL_TO;
@@ -492,7 +493,8 @@
      */
     protected void onPageEndTransition() {
         mCurrentPageScrollDiff = 0;
-        AccessibilityManagerCompat.sendScrollFinishedEventToTest(getContext());
+        AccessibilityManagerCompat.sendTestProtocolEventToTest(getContext(),
+                SCROLL_FINISHED_MESSAGE);
         AccessibilityManagerCompat.sendCustomAccessibilityEvent(getPageAt(mCurrentPage),
                 AccessibilityEvent.TYPE_VIEW_FOCUSED, null);
         if (mOnPageTransitionEndCallback != null) {
diff --git a/src/com/android/launcher3/ShortcutAndWidgetContainer.java b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
index 5e7f21b..b22b690 100644
--- a/src/com/android/launcher3/ShortcutAndWidgetContainer.java
+++ b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
@@ -21,6 +21,7 @@
 import static com.android.launcher3.CellLayout.FOLDER;
 import static com.android.launcher3.CellLayout.HOTSEAT;
 import static com.android.launcher3.CellLayout.WORKSPACE;
+import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_BUBBLE_ADJUSTMENT_ANIM;
 import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_WIDGET_CENTERING;
 
 import android.app.WallpaperManager;
@@ -33,6 +34,8 @@
 import android.view.View;
 import android.view.ViewGroup;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.CellLayout.ContainerType;
 import com.android.launcher3.celllayout.CellLayoutLayoutParams;
 import com.android.launcher3.folder.FolderIcon;
@@ -62,6 +65,9 @@
     private final ActivityContext mActivity;
     private boolean mInvertIfRtl = false;
 
+    @Nullable
+    private TranslationProvider mTranslationProvider = null;
+
     public ShortcutAndWidgetContainer(Context context, @ContainerType int containerType) {
         super(context);
         mActivity = ActivityContext.lookupContext(context);
@@ -229,6 +235,16 @@
                     childLeft, childTop, childLeft + lp.width, childTop + lp.height);
         }
         child.layout(childLeft, childTop, childLeft + lp.width, childTop + lp.height);
+        if (mTranslationProvider != null) {
+            final float tx = mTranslationProvider.getTranslationX(child);
+            if (child instanceof Reorderable) {
+                ((Reorderable) child).getTranslateDelegate()
+                        .getTranslationX(INDEX_BUBBLE_ADJUSTMENT_ANIM)
+                        .setValue(tx);
+            } else {
+                child.setTranslationX(tx);
+            }
+        }
 
         if (lp.dropped) {
             lp.dropped = false;
@@ -298,4 +314,13 @@
             cl.clearFolderLeaveBehind();
         }
     }
+
+    void setTranslationProvider(@Nullable TranslationProvider provider) {
+        mTranslationProvider = provider;
+    }
+
+    /** Provides translation values to apply when laying out child views. */
+    interface TranslationProvider {
+        float getTranslationX(View child);
+    }
 }
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 8be8fed..a1a3974 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -3413,7 +3413,8 @@
             if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) {
                 widgetInfo = widgetHelper.findProvider(item.providerName, item.user);
             } else {
-                widgetInfo = widgetHelper.getLauncherAppWidgetInfo(item.appWidgetId);
+                widgetInfo = widgetHelper.getLauncherAppWidgetInfo(item.appWidgetId,
+                        item.getTargetComponent());
             }
 
             if (widgetInfo != null) {
diff --git a/src/com/android/launcher3/allapps/WorkModeSwitch.java b/src/com/android/launcher3/allapps/WorkModeSwitch.java
index 28a3312..144381c 100644
--- a/src/com/android/launcher3/allapps/WorkModeSwitch.java
+++ b/src/com/android/launcher3/allapps/WorkModeSwitch.java
@@ -17,7 +17,6 @@
 
 import static com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip.getTabWidth;
 
-import android.animation.LayoutTransition;
 import android.content.Context;
 import android.graphics.Rect;
 import android.util.AttributeSet;
@@ -92,8 +91,6 @@
 
         setInsets(mActivityContext.getDeviceProfile().getInsets());
         updateStringFromCache();
-
-        getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING);
     }
 
     @Override
diff --git a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
index 24cc0ac..d37b1f0 100644
--- a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
+++ b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
@@ -74,38 +74,12 @@
         Log.d(TestProtocol.PERMANENT_DIAG_TAG, "sendStateEventToTest: " + stateOrdinal);
     }
 
-    public static void sendScrollFinishedEventToTest(Context context) {
+    public static void sendTestProtocolEventToTest(Context context, String testProtocolEvent) {
         final AccessibilityManager accessibilityManager = getAccessibilityManagerForTest(context);
         if (accessibilityManager == null) return;
 
-        sendEventToTest(accessibilityManager, context, TestProtocol.SCROLL_FINISHED_MESSAGE, null);
+        sendEventToTest(accessibilityManager, context, testProtocolEvent, null);
     }
-
-    public static void sendPauseDetectedEventToTest(Context context) {
-        final AccessibilityManager accessibilityManager = getAccessibilityManagerForTest(context);
-        if (accessibilityManager == null) return;
-
-        sendEventToTest(accessibilityManager, context, TestProtocol.PAUSE_DETECTED_MESSAGE, null);
-    }
-
-    public static void sendDismissAnimationEndsEventToTest(Context context) {
-        final AccessibilityManager accessibilityManager = getAccessibilityManagerForTest(context);
-        if (accessibilityManager == null) return;
-
-        sendEventToTest(accessibilityManager, context, TestProtocol.DISMISS_ANIMATION_ENDS_MESSAGE,
-                null);
-    }
-
-    /**
-     * Notify running tests of a folder opened.
-     */
-    public static void sendFolderOpenedEventToTest(Context context) {
-        final AccessibilityManager accessibilityManager = getAccessibilityManagerForTest(context);
-        if (accessibilityManager == null) return;
-
-        sendEventToTest(accessibilityManager, context, TestProtocol.FOLDER_OPENED_MESSAGE, null);
-    }
-
     private static void sendEventToTest(
             AccessibilityManager accessibilityManager,
             Context context, String eventTag, Bundle data) {
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 4285755..a136ef0 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -27,6 +27,7 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.launcher3.BuildConfig;
+import com.android.launcher3.Flags;
 import com.android.launcher3.Utilities;
 
 import java.util.function.Predicate;
@@ -141,7 +142,7 @@
 
     // TODO(Block 6): Clean up flags
     public static final BooleanFlag ENABLE_ALL_APPS_SEARCH_IN_TASKBAR = getDebugFlag(270393900,
-            "ENABLE_ALL_APPS_SEARCH_IN_TASKBAR", DISABLED,
+            "ENABLE_ALL_APPS_SEARCH_IN_TASKBAR", TEAMFOOD,
             "Enables Search box in Taskbar All Apps.");
 
     public static final BooleanFlag SECONDARY_DRAG_N_DROP_TO_PIN = getDebugFlag(270395140,
@@ -175,22 +176,17 @@
             "MULTI_SELECT_EDIT_MODE", DISABLED, "Enable new multi-select edit mode "
                     + "for home screen");
 
+    public static final BooleanFlag SMARTSPACE_AS_A_WIDGET = getDebugFlag(299181941,
+            "SMARTSPACE_AS_A_WIDGET", DISABLED, "Enable SmartSpace as a widget");
+
     // TODO(Block 10): Clean up flags
     public static final BooleanFlag ENABLE_BACK_SWIPE_LAUNCHER_ANIMATION = getDebugFlag(270614790,
             "ENABLE_BACK_SWIPE_LAUNCHER_ANIMATION", DISABLED,
             "Enables predictive back animation from all apps and widgets to home");
 
     // TODO(Block 11): Clean up flags
-    public static final BooleanFlag ENABLE_TWO_PANEL_HOME = getDebugFlag(270392643,
-            "ENABLE_TWO_PANEL_HOME", ENABLED,
-            "Uses two panel on home screen. Only applicable on large screen devices.");
-
-    public static final BooleanFlag FOLDABLE_WORKSPACE_REORDER = getDebugFlag(270395070,
-            "FOLDABLE_WORKSPACE_REORDER", DISABLED,
-            "In foldables, when reordering the icons and widgets, is now going to use both sides");
-
     public static final BooleanFlag FOLDABLE_SINGLE_PAGE = getDebugFlag(270395274,
-            "FOLDABLE_SINGLE_PAGE", ENABLED, "Use a single page for the workspace");
+            "FOLDABLE_SINGLE_PAGE", DISABLED, "Use a single page for the workspace");
 
     public static final BooleanFlag ENABLE_PARAMETRIZE_REORDER = getDebugFlag(289420844,
             "ENABLE_PARAMETRIZE_REORDER", DISABLED,
@@ -213,6 +209,10 @@
     public static final BooleanFlag ENABLE_TRANSIENT_TASKBAR = getDebugFlag(270395798,
             "ENABLE_TRANSIENT_TASKBAR", ENABLED, "Enables transient taskbar.");
 
+    public static final BooleanFlag ENABLE_TASKBAR_NO_RECREATION = getDebugFlag(299193589,
+            "ENABLE_TASKBAR_NO_RECREATION", DISABLED,
+            "Enables taskbar with no recreation from lifecycle changes of TaskbarActivityContext.");
+
     // TODO(Block 16): Clean up flags
     // When enabled the promise icon is visible in all apps while installation an app.
     public static final BooleanFlag PROMISE_APPS_IN_ALL_APPS = getDebugFlag(270390012,
@@ -308,9 +308,14 @@
                     + "start receiving the events");
 
     // TODO(Block 23): Clean up flags
+    // Aconfig migration complete for ENABLE_GRID_ONLY_OVERVIEW.
+    @VisibleForTesting
     public static final BooleanFlag ENABLE_GRID_ONLY_OVERVIEW = getDebugFlag(270397206,
             "ENABLE_GRID_ONLY_OVERVIEW", TEAMFOOD,
             "Enable a grid-only overview without a focused task.");
+    public static boolean enableGridOnlyOverview() {
+        return ENABLE_GRID_ONLY_OVERVIEW.get() || Flags.enableGridOnlyOverview();
+    }
 
     public static final BooleanFlag ENABLE_CURSOR_HOVER_STATES = getDebugFlag(243191650,
             "ENABLE_CURSOR_HOVER_STATES", TEAMFOOD,
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 55a539a..fc36ce6 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -25,6 +25,7 @@
 import static com.android.launcher3.config.FeatureFlags.ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_LABEL_UPDATED;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED;
+import static com.android.launcher3.testing.shared.TestProtocol.FOLDER_OPENED_MESSAGE;
 import static com.android.launcher3.util.window.RefreshRateTracker.getSingleFrameMs;
 
 import android.animation.Animator;
@@ -692,7 +693,8 @@
             public void onAnimationEnd(Animator animation) {
                 setState(STATE_OPEN);
                 announceAccessibilityChanges();
-                AccessibilityManagerCompat.sendFolderOpenedEventToTest(getContext());
+                AccessibilityManagerCompat.sendTestProtocolEventToTest(getContext(),
+                        FOLDER_OPENED_MESSAGE);
 
                 mContent.setFocusOnFirstChild();
             }
diff --git a/src/com/android/launcher3/model/GridSizeMigrationUtil.java b/src/com/android/launcher3/model/GridSizeMigrationUtil.java
index c233872..78875a3 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationUtil.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationUtil.java
@@ -539,8 +539,8 @@
                             verifyPackage(cn.getPackageName());
 
                             int widgetId = c.getInt(indexAppWidgetId);
-                            LauncherAppWidgetProviderInfo pInfo =
-                                    widgetManagerHelper.getLauncherAppWidgetInfo(widgetId);
+                            LauncherAppWidgetProviderInfo pInfo = widgetManagerHelper
+                                    .getLauncherAppWidgetInfo(widgetId, cn);
                             Point spans = null;
                             if (pInfo != null) {
                                 spans = pInfo.getMinSpans();
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 0e68db2..08a6cf0 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -95,6 +95,7 @@
 import com.android.launcher3.util.TraceHelper;
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.widget.WidgetManagerHelper;
+import com.android.launcher3.widget.custom.CustomWidgetManager;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -740,8 +741,13 @@
 
                     ComponentKey providerKey = new ComponentKey(component, c.user);
                     if (!mWidgetProvidersMap.containsKey(providerKey)) {
-                        mWidgetProvidersMap.put(providerKey,
-                                widgetHelper.findProvider(component, c.user));
+                        if (customWidget) {
+                            mWidgetProvidersMap.put(providerKey, CustomWidgetManager.INSTANCE
+                                    .get(mApp.getContext()).getWidgetProvider(component));
+                        } else {
+                            mWidgetProvidersMap.put(providerKey,
+                                    widgetHelper.findProvider(component, c.user));
+                        }
                     }
                     final AppWidgetProviderInfo provider = mWidgetProvidersMap.get(providerKey);
 
@@ -814,7 +820,8 @@
                             return;
                         }
                         LauncherAppWidgetProviderInfo widgetProviderInfo =
-                                widgetHelper.getLauncherAppWidgetInfo(appWidgetId);
+                                widgetHelper.getLauncherAppWidgetInfo(appWidgetId,
+                                        appWidgetInfo.getTargetComponent());
                         if (widgetProviderInfo != null
                                 && (appWidgetInfo.spanX < widgetProviderInfo.minSpanX
                                 || appWidgetInfo.spanY < widgetProviderInfo.minSpanY)) {
diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index 10005e5..4725dd1 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -28,8 +28,6 @@
 
 import android.app.backup.BackupManager;
 import android.appwidget.AppWidgetHost;
-import android.appwidget.AppWidgetManager;
-import android.appwidget.AppWidgetProviderInfo;
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
@@ -44,26 +42,20 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
-import androidx.annotation.WorkerThread;
 
+import com.android.launcher3.AppWidgetsRestoredReceiver;
 import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherPrefs;
-import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.model.DeviceGridState;
-import com.android.launcher3.model.LoaderTask;
 import com.android.launcher3.model.ModelDbController;
-import com.android.launcher3.model.WidgetsModel;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
 import com.android.launcher3.uioverrides.ApiWrapper;
-import com.android.launcher3.util.ContentWriter;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.LogConfig;
 
@@ -385,13 +377,11 @@
                 .putSync(RESTORE_DEVICE.to(new DeviceGridState(context).getDeviceType()));
     }
 
-    @WorkerThread
-    @VisibleForTesting
-    void restoreAppWidgetIdsIfExists(Context context, ModelDbController controller) {
+    private void restoreAppWidgetIdsIfExists(Context context, ModelDbController controller) {
         LauncherPrefs lp = LauncherPrefs.get(context);
         if (lp.has(APP_WIDGET_IDS, OLD_APP_WIDGET_IDS)) {
             AppWidgetHost host = new AppWidgetHost(context, APPWIDGET_HOST_ID);
-            restoreAppWidgetIds(context, controller,
+            AppWidgetsRestoredReceiver.restoreAppWidgetIds(context, controller,
                     IntArray.fromConcatString(lp.get(OLD_APP_WIDGET_IDS)).toArray(),
                     IntArray.fromConcatString(lp.get(APP_WIDGET_IDS)).toArray(),
                     host);
@@ -402,133 +392,6 @@
         lp.remove(APP_WIDGET_IDS, OLD_APP_WIDGET_IDS);
     }
 
-    /**
-     * Updates the app widgets whose id has changed during the restore process.
-     */
-    @WorkerThread
-    private void restoreAppWidgetIds(Context context, ModelDbController controller,
-            int[] oldWidgetIds, int[] newWidgetIds, @NonNull AppWidgetHost host) {
-        if (WidgetsModel.GO_DISABLE_WIDGETS) {
-            Log.e(TAG, "Skipping widget ID remap as widgets not supported");
-            host.deleteHost();
-            return;
-        }
-        if (!RestoreDbTask.isPending(context)) {
-            // Someone has already gone through our DB once, probably LoaderTask. Skip any further
-            // modifications of the DB.
-            Log.e(TAG, "Skipping widget ID remap as DB already in use");
-            for (int widgetId : newWidgetIds) {
-                Log.d(TAG, "Deleting widgetId: " + widgetId);
-                host.deleteAppWidgetId(widgetId);
-            }
-            return;
-        }
-
-        final AppWidgetManager widgets = AppWidgetManager.getInstance(context);
-
-        Log.d(TAG, "restoreAppWidgetIds: "
-                + "oldWidgetIds=" + IntArray.wrap(oldWidgetIds).toConcatString()
-                + ", newWidgetIds=" + IntArray.wrap(newWidgetIds).toConcatString());
-
-        // TODO(b/234700507): Remove the logs after the bug is fixed
-        logDatabaseWidgetInfo(controller);
-
-        for (int i = 0; i < oldWidgetIds.length; i++) {
-            Log.i(TAG, "Widget state restore id " + oldWidgetIds[i] + " => " + newWidgetIds[i]);
-
-            final AppWidgetProviderInfo provider = widgets.getAppWidgetInfo(newWidgetIds[i]);
-            final int state;
-            if (LoaderTask.isValidProvider(provider)) {
-                // This will ensure that we show 'Click to setup' UI if required.
-                state = LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
-            } else {
-                state = LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
-            }
-
-            // b/135926478: Work profile widget restore is broken in platform. This forces us to
-            // recreate the widget during loading with the correct host provider.
-            long mainProfileId = UserCache.INSTANCE.get(context)
-                    .getSerialNumberForUser(myUserHandle());
-            long controllerProfileId = controller.getSerialNumberForUser(myUserHandle());
-            String oldWidgetId = Integer.toString(oldWidgetIds[i]);
-            final String where = "appWidgetId=? and (restored & 1) = 1 and profileId=?";
-            String profileId = Long.toString(mainProfileId);
-            final String[] args = new String[] { oldWidgetId, profileId };
-            Log.d(TAG, "restoreAppWidgetIds: querying profile id=" + profileId
-                    + " with controller profile ID=" + controllerProfileId);
-            int result = new ContentWriter(context,
-                    new ContentWriter.CommitParams(controller, where, args))
-                    .put(LauncherSettings.Favorites.APPWIDGET_ID, newWidgetIds[i])
-                    .put(LauncherSettings.Favorites.RESTORED, state)
-                    .commit();
-            if (result == 0) {
-                // TODO(b/234700507): Remove the logs after the bug is fixed
-                Log.e(TAG, "restoreAppWidgetIds: remapping failed since the widget is not in"
-                        + " the database anymore");
-                try (Cursor cursor = controller.getDb().query(
-                        Favorites.TABLE_NAME,
-                        new String[]{Favorites.APPWIDGET_ID},
-                        "appWidgetId=?", new String[]{oldWidgetId}, null, null, null)) {
-                    if (!cursor.moveToFirst()) {
-                        // The widget no long exists.
-                        Log.d(TAG, "Deleting widgetId: " + newWidgetIds[i] + " with old id: "
-                                + oldWidgetId);
-                        host.deleteAppWidgetId(newWidgetIds[i]);
-                    }
-                }
-            }
-        }
-
-        LauncherAppState app = LauncherAppState.getInstanceNoCreate();
-        if (app != null) {
-            app.getModel().forceReload();
-        }
-    }
-
-    private static void logDatabaseWidgetInfo(ModelDbController controller) {
-        try (Cursor cursor = controller.getDb().query(Favorites.TABLE_NAME,
-                new String[]{Favorites.APPWIDGET_ID, Favorites.RESTORED, Favorites.PROFILE_ID},
-                Favorites.APPWIDGET_ID + "!=" + LauncherAppWidgetInfo.NO_ID, null,
-                null, null, null)) {
-            IntArray widgetIdList = new IntArray();
-            IntArray widgetRestoreList = new IntArray();
-            IntArray widgetProfileIdList = new IntArray();
-
-            if (cursor.moveToFirst()) {
-                final int widgetIdColumnIndex = cursor.getColumnIndex(Favorites.APPWIDGET_ID);
-                final int widgetRestoredColumnIndex = cursor.getColumnIndex(Favorites.RESTORED);
-                final int widgetProfileIdIndex = cursor.getColumnIndex(Favorites.PROFILE_ID);
-                while (!cursor.isAfterLast()) {
-                    int widgetId = cursor.getInt(widgetIdColumnIndex);
-                    int widgetRestoredFlag = cursor.getInt(widgetRestoredColumnIndex);
-                    int widgetProfileId = cursor.getInt(widgetProfileIdIndex);
-
-                    widgetIdList.add(widgetId);
-                    widgetRestoreList.add(widgetRestoredFlag);
-                    widgetProfileIdList.add(widgetProfileId);
-                    cursor.moveToNext();
-                }
-            }
-
-            StringBuilder builder = new StringBuilder();
-            builder.append("[");
-            for (int i = 0; i < widgetIdList.size(); i++) {
-                builder.append("[")
-                        .append(widgetIdList.get(i))
-                        .append(", ")
-                        .append(widgetRestoreList.get(i))
-                        .append(", ")
-                        .append(widgetProfileIdList.get(i))
-                        .append("]");
-            }
-            builder.append("]");
-            Log.d(TAG, "restoreAppWidgetIds: all widget ids in database: "
-                    + builder.toString());
-        } catch (Exception ex) {
-            Log.e(TAG, "Getting widget ids from the database failed", ex);
-        }
-    }
-
     public static void setRestoredAppWidgetIds(Context context, @NonNull int[] oldIds,
             @NonNull int[] newIds) {
         LauncherPrefs.get(context).putSync(
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index 07a1b82..a75f326 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -16,7 +16,7 @@
 package com.android.launcher3.testing;
 
 import static com.android.launcher3.allapps.AllAppsStore.DEFER_UPDATES_TEST;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_GRID_ONLY_OVERVIEW;
+import static com.android.launcher3.config.FeatureFlags.enableGridOnlyOverview;
 import static com.android.launcher3.config.FeatureFlags.FOLDABLE_SINGLE_PAGE;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
@@ -32,6 +32,7 @@
 import android.view.WindowInsets;
 
 import androidx.annotation.Nullable;
+import androidx.core.view.WindowInsetsCompat;
 
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeviceProfile;
@@ -142,6 +143,14 @@
                 }, this::getCurrentActivity);
             }
 
+            case TestProtocol.REQUEST_IME_INSETS: {
+                return getUIProperty(Bundle::putParcelable, activity -> {
+                    WindowInsetsCompat insets = WindowInsetsCompat.toWindowInsetsCompat(
+                            activity.getWindow().getDecorView().getRootWindowInsets());
+                    return insets.getInsets(WindowInsetsCompat.Type.ime()).toPlatformInsets();
+                }, this::getCurrentActivity);
+            }
+
             case TestProtocol.REQUEST_ICON_HEIGHT: {
                 response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD,
                         mDeviceProfile.allAppsCellHeightPx);
@@ -249,7 +258,7 @@
 
             case TestProtocol.REQUEST_FLAG_ENABLE_GRID_ONLY_OVERVIEW: {
                 response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD,
-                        ENABLE_GRID_ONLY_OVERVIEW.get());
+                        enableGridOnlyOverview());
                 return response;
             }
 
diff --git a/src/com/android/launcher3/util/MultiTranslateDelegate.java b/src/com/android/launcher3/util/MultiTranslateDelegate.java
index 1cb7a45..0e32ba7 100644
--- a/src/com/android/launcher3/util/MultiTranslateDelegate.java
+++ b/src/com/android/launcher3/util/MultiTranslateDelegate.java
@@ -40,6 +40,9 @@
     // Specific for widgets
     public static final int INDEX_WIDGET_CENTERING = 3;
 
+    // Specific for hotseat items when adjusting for bubbles
+    public static final int INDEX_BUBBLE_ADJUSTMENT_ANIM = 3;
+
     public static final int COUNT = 5;
 
     private final MultiPropertyFactory<View> mTranslationX;
diff --git a/src/com/android/launcher3/views/ActivityContext.java b/src/com/android/launcher3/views/ActivityContext.java
index 84ea871..7b82195 100644
--- a/src/com/android/launcher3/views/ActivityContext.java
+++ b/src/com/android/launcher3/views/ActivityContext.java
@@ -442,6 +442,16 @@
         return CellPosMapper.DEFAULT;
     }
 
+    /** Whether bubbles are enabled. */
+    default boolean isBubbleBarEnabled() {
+        return false;
+    }
+
+    /** Whether the bubble bar has bubbles. */
+    default boolean hasBubbles() {
+        return false;
+    }
+
     /**
      * Returns the ActivityContext associated with the given Context, or throws an exception if
      * the Context is not associated with any ActivityContext.
diff --git a/src/com/android/launcher3/views/ArrowTipView.java b/src/com/android/launcher3/views/ArrowTipView.java
index b44dbeb..2f0da03 100644
--- a/src/com/android/launcher3/views/ArrowTipView.java
+++ b/src/com/android/launcher3/views/ArrowTipView.java
@@ -155,7 +155,7 @@
         setOrientation(LinearLayout.VERTICAL);
 
         mArrowView = findViewById(R.id.arrow);
-        updateArrowTipInView();
+        updateArrowTipInView(mIsPointingUp);
         setAlpha(0);
 
         // Create default open animator.
@@ -364,17 +364,18 @@
 
             // Adjust the tooltip vertically.
             @Px int viewHeight = getHeight();
+            boolean isPointingUp = mIsPointingUp;
             if (mIsPointingUp
                     ? (yCoordUpPointingTip + viewHeight > parentViewHeight)
                     : (yCoordDownPointingTip - viewHeight < 0)) {
                 // Flip the view if it exceeds the vertical bounds of screen.
-                mIsPointingUp = !mIsPointingUp;
-                updateArrowTipInView();
+                isPointingUp = !mIsPointingUp;
             }
+            updateArrowTipInView(isPointingUp);
             // Place the tooltip such that its top is at yCoordUpPointingTip if arrow is displayed
             // pointing upwards, otherwise place it such that its bottom is at
             // yCoordDownPointingTip.
-            setY(mIsPointingUp ? yCoordUpPointingTip : yCoordDownPointingTip - viewHeight);
+            setY(isPointingUp ? yCoordUpPointingTip : yCoordDownPointingTip - viewHeight);
 
             // Adjust the arrow's relative position on tooltip to make sure the actual position of
             // arrow's pointed tip is always at arrowXCoord.
@@ -391,10 +392,10 @@
         return this;
     }
 
-    private void updateArrowTipInView() {
+    private void updateArrowTipInView(boolean isPointingUp) {
         ViewGroup.LayoutParams arrowLp = mArrowView.getLayoutParams();
         ShapeDrawable arrowDrawable = new ShapeDrawable(TriangleShape.create(
-                arrowLp.width, arrowLp.height, mIsPointingUp));
+                arrowLp.width, arrowLp.height, isPointingUp));
         Paint arrowPaint = arrowDrawable.getPaint();
         @Px int arrowTipRadius = getContext().getResources()
                 .getDimensionPixelSize(R.dimen.arrow_toast_corner_radius);
@@ -403,7 +404,7 @@
         mArrowView.setBackground(arrowDrawable);
         // Add negative margin so that the rounded corners on base of arrow are not visible.
         removeView(mArrowView);
-        if (mIsPointingUp) {
+        if (isPointingUp) {
             addView(mArrowView, 0);
             ((ViewGroup.MarginLayoutParams) arrowLp).setMargins(0, 0, 0, -1 * arrowTipRadius);
         } else {
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
index 76a044d..f64fd05 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
@@ -23,9 +23,11 @@
 import android.graphics.Rect;
 import android.os.Build;
 import android.os.Handler;
+import android.os.Parcelable;
 import android.os.SystemClock;
 import android.os.Trace;
 import android.util.Log;
+import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
 import android.view.MotionEvent;
@@ -498,4 +500,13 @@
          */
         void notifyBoundChangeOnPreLayout(View v, int left, int top, int right, int bottom);
     }
+
+    @Override
+    protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
+        try {
+            super.dispatchRestoreInstanceState(container);
+        } catch (Exception e) {
+            Log.i(TAG, "Exception: " + e);
+        }
+    }
 }
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java b/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java
index 10aef9a..ef51d15 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java
@@ -67,7 +67,7 @@
      */
     public int maxSpanY;
 
-    private boolean mIsMinSizeFulfilled;
+    protected boolean mIsMinSizeFulfilled;
 
     public static LauncherAppWidgetProviderInfo fromProviderInfo(Context context,
             AppWidgetProviderInfo info) {
diff --git a/src/com/android/launcher3/widget/WidgetManagerHelper.java b/src/com/android/launcher3/widget/WidgetManagerHelper.java
index 737cdbd..0860e72 100644
--- a/src/com/android/launcher3/widget/WidgetManagerHelper.java
+++ b/src/com/android/launcher3/widget/WidgetManagerHelper.java
@@ -57,10 +57,15 @@
     /**
      * @see AppWidgetManager#getAppWidgetInfo(int)
      */
-    public LauncherAppWidgetProviderInfo getLauncherAppWidgetInfo(int appWidgetId) {
-        if (appWidgetId <= LauncherAppWidgetInfo.CUSTOM_WIDGET_ID) {
-            return CustomWidgetManager.INSTANCE.get(mContext).getWidgetProvider(appWidgetId);
+    public LauncherAppWidgetProviderInfo getLauncherAppWidgetInfo(
+            int appWidgetId, ComponentName componentName) {
+
+        // For custom widgets.
+        if (appWidgetId <= LauncherAppWidgetInfo.CUSTOM_WIDGET_ID && !CustomWidgetManager
+                .INSTANCE.get(mContext).getWidgetIdForCustomProvider(componentName).equals("")) {
+            return CustomWidgetManager.INSTANCE.get(mContext).getWidgetProvider(componentName);
         }
+
         AppWidgetProviderInfo info = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
         return info == null ? null : LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, info);
     }
diff --git a/src/com/android/launcher3/widget/custom/CustomAppWidgetProviderInfo.java b/src/com/android/launcher3/widget/custom/CustomAppWidgetProviderInfo.java
index 8b3bbce..44571a6 100644
--- a/src/com/android/launcher3/widget/custom/CustomAppWidgetProviderInfo.java
+++ b/src/com/android/launcher3/widget/custom/CustomAppWidgetProviderInfo.java
@@ -33,14 +33,15 @@
 public class CustomAppWidgetProviderInfo extends LauncherAppWidgetProviderInfo
         implements Parcelable {
 
-    public final int providerId;
+    public final String providerId;
 
-    protected CustomAppWidgetProviderInfo(Parcel parcel, boolean readSelf, int providerId) {
+    protected CustomAppWidgetProviderInfo(Parcel parcel, boolean readSelf, String providerId) {
         super(parcel);
         if (readSelf) {
-            this.providerId = parcel.readInt();
+            this.providerId = parcel.readString();
 
-            provider = new ComponentName(parcel.readString(), CLS_CUSTOM_WIDGET_PREFIX + providerId);
+            provider = new ComponentName(parcel.readString(),
+                    CLS_CUSTOM_WIDGET_PREFIX + parcel.readString());
 
             label = parcel.readString();
             initialLayout = parcel.readInt();
@@ -58,7 +59,10 @@
     }
 
     @Override
-    public void initSpans(Context context, InvariantDeviceProfile idp) { }
+    public void initSpans(Context context, InvariantDeviceProfile idp) {
+        mIsMinSizeFulfilled = Math.min(spanX, minSpanX) <= idp.numColumns
+                && Math.min(spanY, minSpanY) <= idp.numRows;
+    }
 
     @Override
     public String getLabel(PackageManager packageManager) {
@@ -73,8 +77,9 @@
     @Override
     public void writeToParcel(Parcel out, int flags) {
         super.writeToParcel(out, flags);
-        out.writeInt(providerId);
+        out.writeString(providerId);
         out.writeString(provider.getPackageName());
+        out.writeString(provider.getClassName());
 
         out.writeString(label);
         out.writeInt(initialLayout);
@@ -93,7 +98,7 @@
 
         @Override
         public CustomAppWidgetProviderInfo createFromParcel(Parcel parcel) {
-            return new CustomAppWidgetProviderInfo(parcel, true, 0);
+            return new CustomAppWidgetProviderInfo(parcel, true, "");
         }
 
         @Override
diff --git a/src/com/android/launcher3/widget/custom/CustomWidgetManager.java b/src/com/android/launcher3/widget/custom/CustomWidgetManager.java
index 2e2a968..7cf0221 100644
--- a/src/com/android/launcher3/widget/custom/CustomWidgetManager.java
+++ b/src/com/android/launcher3/widget/custom/CustomWidgetManager.java
@@ -16,7 +16,8 @@
 
 package com.android.launcher3.widget.custom;
 
-import static com.android.launcher3.widget.LauncherAppWidgetProviderInfo.CLS_CUSTOM_WIDGET_PREFIX;
+import static com.android.launcher3.config.FeatureFlags.SMARTSPACE_AS_A_WIDGET;
+import static com.android.launcher3.model.data.LauncherAppWidgetInfo.CUSTOM_WIDGET_ID;
 
 import android.appwidget.AppWidgetManager;
 import android.appwidget.AppWidgetProviderInfo;
@@ -24,12 +25,12 @@
 import android.content.Context;
 import android.os.Parcel;
 import android.os.Process;
-import android.util.SparseArray;
+import android.util.Log;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
-import com.android.launcher3.model.data.LauncherAppWidgetInfo;
+import com.android.launcher3.R;
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.PackageUserKey;
@@ -39,8 +40,11 @@
 import com.android.systemui.plugins.CustomWidgetPlugin;
 import com.android.systemui.plugins.PluginListener;
 
+import java.lang.reflect.InvocationTargetException;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.function.Consumer;
 import java.util.stream.Stream;
 
@@ -52,24 +56,37 @@
     public static final MainThreadInitializedObject<CustomWidgetManager> INSTANCE =
             new MainThreadInitializedObject<>(CustomWidgetManager::new);
 
+    private static final String TAG = "CustomWidgetManager";
     private final Context mContext;
-    /**
-     * auto provider Id is an ever-increasing number that serves as the providerId whenever a new
-     * custom widget has been connected.
-     */
-    private int mAutoProviderId = 0;
-    private final SparseArray<CustomWidgetPlugin> mPlugins;
+    private final HashMap<String, CustomWidgetPlugin> mPlugins;
     private final List<CustomAppWidgetProviderInfo> mCustomWidgets;
-    private final SparseArray<ComponentName> mWidgetsIdMap;
+    private final HashMap<ComponentName, String> mWidgetsIdMap;
     private Consumer<PackageUserKey> mWidgetRefreshCallback;
 
     private CustomWidgetManager(Context context) {
         mContext = context;
-        mPlugins = new SparseArray<>();
+        mPlugins = new HashMap<>();
         mCustomWidgets = new ArrayList<>();
-        mWidgetsIdMap = new SparseArray<>();
+        mWidgetsIdMap = new HashMap<>();
         PluginManagerWrapper.INSTANCE.get(context)
                 .addPluginListener(this, CustomWidgetPlugin.class, true);
+
+        if (SMARTSPACE_AS_A_WIDGET.get()) {
+            for (String s: context.getResources()
+                    .getStringArray(R.array.custom_widget_providers)) {
+                try {
+                    Class<?> cls = Class.forName(s);
+                    CustomWidgetPlugin plugin = (CustomWidgetPlugin)
+                            cls.getDeclaredConstructor(Context.class).newInstance(context);
+                    mPlugins.put(plugin.getId(), plugin);
+                    onPluginConnected(mPlugins.get(plugin.getId()), context);
+                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException
+                         | ClassCastException | NoSuchMethodException
+                         | InvocationTargetException e) {
+                    Log.e(TAG, "Exception found when trying to add custom widgets: " + e);
+                }
+            }
+        }
     }
 
     @Override
@@ -79,28 +96,41 @@
 
     @Override
     public void onPluginConnected(CustomWidgetPlugin plugin, Context context) {
-        mPlugins.put(mAutoProviderId, plugin);
         List<AppWidgetProviderInfo> providers = AppWidgetManager.getInstance(context)
                 .getInstalledProvidersForProfile(Process.myUserHandle());
         if (providers.isEmpty()) return;
         Parcel parcel = Parcel.obtain();
         providers.get(0).writeToParcel(parcel, 0);
         parcel.setDataPosition(0);
-        CustomAppWidgetProviderInfo info = newInfo(mAutoProviderId, plugin, parcel, context);
+        CustomAppWidgetProviderInfo info = newInfo(plugin.getId(), plugin, parcel, context);
         parcel.recycle();
         mCustomWidgets.add(info);
-        mWidgetsIdMap.put(mAutoProviderId, info.provider);
-        mWidgetRefreshCallback.accept(null);
-        mAutoProviderId++;
+        mWidgetsIdMap.put(info.provider, plugin.getId());
     }
 
     @Override
     public void onPluginDisconnected(CustomWidgetPlugin plugin) {
-        int providerId = findProviderId(plugin);
-        if (providerId == -1) return;
-        mPlugins.remove(providerId);
-        mCustomWidgets.remove(getWidgetProvider(providerId));
-        mWidgetsIdMap.remove(providerId);
+        String providerId = plugin.getId();
+        if (mPlugins.containsKey(providerId)) {
+            mPlugins.remove(providerId);
+        }
+
+        ComponentName cn = null;
+        for (Map.Entry entry: mWidgetsIdMap.entrySet()) {
+            if (entry.getValue().equals(providerId)) {
+                cn = (ComponentName) entry.getKey();
+            }
+        }
+
+        if (cn != null) {
+            mWidgetsIdMap.remove(cn);
+            for (int i = 0; i < mCustomWidgets.size(); i++) {
+                if (mCustomWidgets.get(i).getComponent().equals(cn)) {
+                    mCustomWidgets.remove(i);
+                    return;
+                }
+            }
+        }
     }
 
     /**
@@ -131,12 +161,11 @@
     /**
      * Returns the widget id for a specific provider.
      */
-    public int getWidgetIdForCustomProvider(@NonNull ComponentName provider) {
-        int index = mWidgetsIdMap.indexOfValue(provider);
-        if (index >= 0) {
-            return LauncherAppWidgetInfo.CUSTOM_WIDGET_ID - mWidgetsIdMap.keyAt(index);
+    public String getWidgetIdForCustomProvider(@NonNull ComponentName provider) {
+        if (mWidgetsIdMap.containsKey(provider)) {
+            return mWidgetsIdMap.get(provider);
         } else {
-            return AppWidgetManager.INVALID_APPWIDGET_ID;
+            return "";
         }
     }
 
@@ -144,38 +173,26 @@
      * Returns the widget provider in respect to given widget id.
      */
     @Nullable
-    public LauncherAppWidgetProviderInfo getWidgetProvider(int widgetId) {
-        ComponentName cn = mWidgetsIdMap.get(LauncherAppWidgetInfo.CUSTOM_WIDGET_ID - widgetId);
+    public LauncherAppWidgetProviderInfo getWidgetProvider(ComponentName componentName) {
         for (LauncherAppWidgetProviderInfo info : mCustomWidgets) {
-            if (info.provider.equals(cn)) return info;
+            if (info.provider.equals(componentName)) return info;
         }
         return null;
     }
 
-    private static CustomAppWidgetProviderInfo newInfo(int providerId, CustomWidgetPlugin plugin,
+    private static CustomAppWidgetProviderInfo newInfo(String providerId, CustomWidgetPlugin plugin,
             Parcel parcel, Context context) {
         CustomAppWidgetProviderInfo info = new CustomAppWidgetProviderInfo(
                 parcel, false, providerId);
-        info.provider = new ComponentName(
-                context.getPackageName(), CLS_CUSTOM_WIDGET_PREFIX + providerId);
-
-        info.label = plugin.getLabel();
-        info.resizeMode = plugin.getResizeMode();
-
-        info.spanX = plugin.getSpanX();
-        info.spanY = plugin.getSpanY();
-        info.minSpanX = plugin.getMinSpanX();
-        info.minSpanY = plugin.getMinSpanY();
+        plugin.updateWidgetInfo(info, context);
         return info;
     }
 
-    private int findProviderId(CustomWidgetPlugin plugin) {
-        for (int i = 0; i < mPlugins.size(); i++) {
-            int providerId = mPlugins.keyAt(i);
-            if (mPlugins.get(providerId) == plugin) {
-                return providerId;
-            }
-        }
-        return -1;
+    /**
+     * Returns an id to set as the appWidgetId for a custom widget.
+     */
+    public int allocateCustomAppWidgetId(ComponentName componentName) {
+        return CUSTOM_WIDGET_ID - mCustomWidgets.indexOf(getWidgetProvider(componentName));
     }
+
 }
diff --git a/src_plugins/com/android/systemui/plugins/CustomWidgetPlugin.java b/src_plugins/com/android/systemui/plugins/CustomWidgetPlugin.java
index 56ebcc5..af4f22c 100644
--- a/src_plugins/com/android/systemui/plugins/CustomWidgetPlugin.java
+++ b/src_plugins/com/android/systemui/plugins/CustomWidgetPlugin.java
@@ -17,6 +17,8 @@
 package com.android.systemui.plugins;
 
 import android.appwidget.AppWidgetHostView;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.Context;
 
 import com.android.systemui.plugins.annotations.ProvidesInterface;
 
@@ -30,42 +32,20 @@
     int VERSION = 1;
 
     /**
-     * The label to display to the user in the AppWidget picker.
-     */
-    String getLabel();
-
-    /**
-     * The default width of the widget when added to a host, in dp. The widget will get
-     * at least this width, and will often be given more, depending on the host.
-     */
-    int getSpanX();
-
-    /**
-     * The default height of the widget when added to a host, in dp. The widget will get
-     * at least this height, and will often be given more, depending on the host.
-     */
-    int getSpanY();
-
-    /**
-     * Minimum width (in dp) which the widget can be resized to. This field has no effect if it
-     * is greater than minWidth or if horizontal resizing isn't enabled.
-     */
-    int getMinSpanX();
-
-    /**
-     * Minimum height (in dp) which the widget can be resized to. This field has no effect if it
-     * is greater than minHeight or if vertical resizing isn't enabled.
-     */
-    int getMinSpanY();
-
-    /**
-     * The rules by which a widget can be resized.
-     */
-    int getResizeMode();
-
-    /**
      * Notify the plugin that container of the widget has been rendered, where the custom widget
      * can be attached to.
      */
     void onViewCreated(AppWidgetHostView parent);
+
+    /**
+     * Get the UUID for the custom widget.
+     */
+    String getId();
+
+    /**
+     * Used to modify a widgets' info.
+     */
+    default void updateWidgetInfo(AppWidgetProviderInfo info, Context context) {
+
+    }
 }
diff --git a/tests/Android.bp b/tests/Android.bp
index d45e9eb..9bb8787 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -55,6 +55,7 @@
       "src/com/android/launcher3/util/rule/SamplerRule.java",
       "src/com/android/launcher3/util/rule/ScreenRecordRule.java",
       "src/com/android/launcher3/util/rule/ShellCommandRule.java",
+      "src/com/android/launcher3/util/rule/TestIsolationRule.java",
       "src/com/android/launcher3/util/rule/TestStabilityRule.java",
       "src/com/android/launcher3/util/rule/TISBindRule.java",
       "src/com/android/launcher3/util/viewcapture_analysis/*.java",
diff --git a/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java b/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
index 9c95397..30ec5cc 100644
--- a/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
+++ b/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
@@ -28,6 +28,7 @@
     public static final String PAUSE_DETECTED_MESSAGE = "TAPL_PAUSE_DETECTED";
     public static final String DISMISS_ANIMATION_ENDS_MESSAGE = "TAPL_DISMISS_ANIMATION_ENDS";
     public static final String FOLDER_OPENED_MESSAGE = "TAPL_FOLDER_OPENED";
+    public static final String SEARCH_RESULT_COMPLETE = "SEARCH_RESULT_COMPLETE";
     public static final int NORMAL_STATE_ORDINAL = 0;
     public static final int SPRING_LOADED_STATE_ORDINAL = 1;
     public static final int OVERVIEW_STATE_ORDINAL = 2;
@@ -102,6 +103,7 @@
     public static final String REQUEST_WIDGETS_SCROLL_Y = "widgets-scroll-y";
     public static final String REQUEST_TARGET_INSETS = "target-insets";
     public static final String REQUEST_WINDOW_INSETS = "window-insets";
+    public static final String REQUEST_IME_INSETS = "ime-insets";
     public static final String REQUEST_PID = "pid";
     public static final String REQUEST_FORCE_GC = "gc";
     public static final String REQUEST_RECENT_TASKS_LIST = "recent-tasks-list";
diff --git a/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java b/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
index 15f6177..73bb586 100644
--- a/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
+++ b/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
@@ -19,30 +19,17 @@
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
-import static com.android.launcher3.LauncherPrefs.APP_WIDGET_IDS;
-import static com.android.launcher3.LauncherPrefs.OLD_APP_WIDGET_IDS;
-import static com.android.launcher3.LauncherPrefs.RESTORE_DEVICE;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
 import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
-import static com.android.launcher3.widget.LauncherWidgetHolder.APPWIDGET_HOST_ID;
-
-import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
-import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
 
 import android.app.backup.BackupManager;
-import android.appwidget.AppWidgetHost;
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
@@ -56,7 +43,6 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.model.ModelDbController;
@@ -66,10 +52,6 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.Mockito;
-
-import java.util.Arrays;
-import java.util.stream.IntStream;
 
 /**
  * Tests for {@link RestoreDbTask}
@@ -84,21 +66,11 @@
 
     private LauncherModelHelper mModelHelper;
     private Context mContext;
-    private RestoreDbTask mTask;
-    private ModelDbController mMockController;
-    private SQLiteDatabase mMockDb;
-    private Cursor mMockCursor;
-    private LauncherPrefs mPrefs;
 
     @Before
     public void setup() {
         mModelHelper = new LauncherModelHelper();
         mContext = mModelHelper.sandboxContext;
-        mTask = new RestoreDbTask();
-        mMockController = Mockito.mock(ModelDbController.class);
-        mMockDb = mock(SQLiteDatabase.class);
-        mMockCursor = mock(Cursor.class);
-        mPrefs = new LauncherPrefs(mContext);
     }
 
     @After
@@ -176,7 +148,8 @@
         assertEquals(10, getItemCountForProfile(db, myProfileId_old));
         assertEquals(6, getItemCountForProfile(db, workProfileId_old));
 
-        mTask.sanitizeDB(mContext, controller, controller.getDb(), bm);
+        RestoreDbTask task = new RestoreDbTask();
+        task.sanitizeDB(mContext, controller, controller.getDb(), bm);
 
         // All the data has been migrated to the new user ids
         assertEquals(0, getItemCountForProfile(db, myProfileId_old));
@@ -205,7 +178,8 @@
         assertEquals(10, getItemCountForProfile(db, myProfileId_old));
         assertEquals(6, getItemCountForProfile(db, workProfileId_old));
 
-        mTask.sanitizeDB(mContext, controller, controller.getDb(), bm);
+        RestoreDbTask task = new RestoreDbTask();
+        task.sanitizeDB(mContext, controller, controller.getDb(), bm);
 
         // All the data has been migrated to the new user ids
         assertEquals(0, getItemCountForProfile(db, myProfileId_old));
@@ -214,83 +188,6 @@
         assertEquals(10, getCount(db, "select * from favorites"));
     }
 
-    @Test
-    public void givenLauncherPrefsHasNoIds_whenRestoreAppWidgetIdsIfExists_thenIdsAreRemoved() {
-        // When
-        mTask.restoreAppWidgetIdsIfExists(mContext, mMockController);
-        // Then
-        assertThat(mPrefs.has(OLD_APP_WIDGET_IDS, APP_WIDGET_IDS)).isFalse();
-    }
-
-    @Test
-    public void givenNoPendingRestore_WhenRestoreAppWidgetIds_ThenRemoveNewWidgetIds() {
-        // Given
-        AppWidgetHost expectedHost = new AppWidgetHost(mContext, APPWIDGET_HOST_ID);
-        int[] expectedOldIds = generateOldWidgetIds(expectedHost);
-        int[] expectedNewIds = generateNewWidgetIds(expectedHost, expectedOldIds);
-        when(mMockController.getDb()).thenReturn(mMockDb);
-        mPrefs.remove(RESTORE_DEVICE);
-
-        // When
-        RestoreDbTask.setRestoredAppWidgetIds(mContext, expectedOldIds, expectedNewIds);
-        mTask.restoreAppWidgetIdsIfExists(mContext, mMockController);
-
-        // Then
-        assertThat(expectedHost.getAppWidgetIds()).isEqualTo(expectedOldIds);
-        assertThat(mPrefs.has(OLD_APP_WIDGET_IDS, APP_WIDGET_IDS)).isFalse();
-        verifyZeroInteractions(mMockController);
-    }
-
-    @Test
-    public void givenRestoreWithNonExistingWidgets_WhenRestoreAppWidgetIds_ThenRemoveNewIds() {
-        // Given
-        AppWidgetHost expectedHost = new AppWidgetHost(mContext, APPWIDGET_HOST_ID);
-        int[] expectedOldIds = generateOldWidgetIds(expectedHost);
-        int[] expectedNewIds = generateNewWidgetIds(expectedHost, expectedOldIds);
-        when(mMockController.getDb()).thenReturn(mMockDb);
-        when(mMockDb.query(any(), any(), any(), any(), any(), any(), any())).thenReturn(
-                mMockCursor);
-        when(mMockCursor.moveToFirst()).thenReturn(false);
-        RestoreDbTask.setPending(mContext);
-
-        // When
-        RestoreDbTask.setRestoredAppWidgetIds(mContext, expectedOldIds, expectedNewIds);
-        mTask.restoreAppWidgetIdsIfExists(mContext, mMockController);
-
-        // Then
-        assertThat(expectedHost.getAppWidgetIds()).isEqualTo(expectedOldIds);
-        assertThat(mPrefs.has(OLD_APP_WIDGET_IDS, APP_WIDGET_IDS)).isFalse();
-        verify(mMockController, times(expectedOldIds.length)).update(any(), any(), any(), any());
-    }
-
-    @Test
-    public void givenRestore_WhenRestoreAppWidgetIds_ThenAddNewIds() {
-        // Given
-        AppWidgetHost expectedHost = new AppWidgetHost(mContext, APPWIDGET_HOST_ID);
-        int[] expectedOldIds = generateOldWidgetIds(expectedHost);
-        int[] expectedNewIds = generateNewWidgetIds(expectedHost, expectedOldIds);
-        int[] allExpectedIds = IntStream.concat(
-                Arrays.stream(expectedOldIds),
-                Arrays.stream(expectedNewIds)
-        ).toArray();
-
-        when(mMockController.getDb()).thenReturn(mMockDb);
-        when(mMockDb.query(any(), any(), any(), any(), any(), any(), any()))
-                .thenReturn(mMockCursor);
-        when(mMockCursor.moveToFirst()).thenReturn(true);
-        when(mMockCursor.isAfterLast()).thenReturn(true);
-        RestoreDbTask.setPending(mContext);
-
-        // When
-        RestoreDbTask.setRestoredAppWidgetIds(mContext, expectedOldIds, expectedNewIds);
-        mTask.restoreAppWidgetIdsIfExists(mContext, mMockController);
-
-        // Then
-        assertThat(expectedHost.getAppWidgetIds()).isEqualTo(allExpectedIds);
-        assertThat(mPrefs.has(OLD_APP_WIDGET_IDS, APP_WIDGET_IDS)).isFalse();
-        verify(mMockController, times(expectedOldIds.length)).update(any(), any(), any(), any());
-    }
-
     private void addIconsBulk(MyModelDbController controller,
             int count, int screen, long profileId) {
         int columns = LauncherAppState.getIDP(mContext).numColumns;
@@ -373,19 +270,6 @@
         }
     }
 
-    private int[] generateOldWidgetIds(AppWidgetHost host) {
-        // generate some widget ids in case there are none
-        host.allocateAppWidgetId();
-        host.allocateAppWidgetId();
-        return host.getAppWidgetIds();
-    }
-
-    private int[] generateNewWidgetIds(AppWidgetHost host, int[] oldWidgetIds) {
-        // map as many new ids as old ids
-        return Arrays.stream(oldWidgetIds)
-                .map(id -> host.allocateAppWidgetId()).toArray();
-    }
-
     private class MyModelDbController extends ModelDbController {
 
         public final LongSparseArray<UserHandle> users = new LongSparseArray<>();
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 5240e6a..707781e 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -16,11 +16,9 @@
 package com.android.launcher3.ui;
 
 import static androidx.test.InstrumentationRegistry.getInstrumentation;
-
 import static com.android.launcher3.testing.shared.TestProtocol.ICON_MISSING;
 import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
@@ -68,6 +66,7 @@
 import com.android.launcher3.util.rule.SamplerRule;
 import com.android.launcher3.util.rule.ScreenRecordRule;
 import com.android.launcher3.util.rule.ShellCommandRule;
+import com.android.launcher3.util.rule.TestIsolationRule;
 import com.android.launcher3.util.rule.TestStabilityRule;
 import com.android.launcher3.util.rule.ViewCaptureRule;
 
@@ -206,7 +205,8 @@
         final RuleChain inner = RuleChain
                 .outerRule(new PortraitLandscapeRunner(this))
                 .around(new FailureWatcher(mLauncher, viewCaptureRule::getViewCaptureData))
-                .around(viewCaptureRule);
+                .around(viewCaptureRule)
+                .around(new TestIsolationRule(mLauncher));
 
         return TestHelpers.isInLauncherProcess()
                 ? RuleChain.outerRule(ShellCommandRule.setDefaultLauncher()).around(inner)
diff --git a/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java b/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
index f0875f8..ad11268 100644
--- a/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
+++ b/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
@@ -1,9 +1,12 @@
 package com.android.launcher3.ui;
 
+import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_PRESUBMIT;
+
 import android.util.Log;
 import android.view.Surface;
 
 import com.android.launcher3.tapl.TestHelpers;
+import com.android.launcher3.util.rule.TestStabilityRule;
 
 import org.junit.rules.TestRule;
 import org.junit.runner.Description;
@@ -30,8 +33,12 @@
 
     @Override
     public Statement apply(Statement base, Description description) {
-        if (!TestHelpers.isInLauncherProcess() ||
-                description.getAnnotation(PortraitLandscape.class) == null) {
+        if (!TestHelpers.isInLauncherProcess()
+                || description.getAnnotation(PortraitLandscape.class) == null
+                // If running in presubmit, don't run in both orientations.
+                // It's important to keep presubmits fast even if we will occasionally miss
+                // regressions in presubmit.
+                || TestStabilityRule.getRunFlavor() == PLATFORM_PRESUBMIT) {
             return base;
         }
 
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index abb193a..0316f84 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -627,4 +627,28 @@
             allApps.unfreeze();
         }
     }
+
+    @PlatinumTest(focusArea = "launcher")
+    @Test
+    public void testAddAndDeletePageAndFling() {
+        Workspace workspace = mLauncher.getWorkspace();
+        // Get the first app from the hotseat
+        HomeAppIcon hotSeatIcon = workspace.getHotseatAppIcon(0);
+        String appName = hotSeatIcon.getIconName();
+
+        // Add one page by dragging app to page 1.
+        workspace.dragIcon(hotSeatIcon, workspace.pagesPerScreen());
+        assertEquals("Incorrect Page count Number",
+                workspace.pagesPerScreen() * 2,
+                workspace.getPageCount());
+
+        // Delete one page by dragging app to hot seat.
+        workspace.getWorkspaceAppIcon(appName).dragToHotseat(0);
+
+        // Refresh workspace to avoid using stale container error.
+        workspace = mLauncher.getWorkspace();
+        assertEquals("Incorrect Page count Number",
+                workspace.pagesPerScreen(),
+                workspace.getPageCount());
+    }
 }
diff --git a/tests/src/com/android/launcher3/ui/WorkProfileTest.java b/tests/src/com/android/launcher3/ui/WorkProfileTest.java
index 5b9adcd..ac710fd 100644
--- a/tests/src/com/android/launcher3/ui/WorkProfileTest.java
+++ b/tests/src/com/android/launcher3/ui/WorkProfileTest.java
@@ -19,8 +19,6 @@
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.allapps.AllAppsStore.DEFER_UPDATES_TEST;
 import static com.android.launcher3.util.TestUtil.installDummyAppForUser;
-import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
-import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
@@ -39,7 +37,6 @@
 import com.android.launcher3.allapps.WorkProfileManager;
 import com.android.launcher3.tapl.LauncherInstrumentation;
 import com.android.launcher3.util.TestUtil;
-import com.android.launcher3.util.rule.TestStabilityRule.Stability;
 
 import org.junit.After;
 import org.junit.Before;
@@ -103,8 +100,6 @@
     }
 
     private void waitForWorkTabSetup() {
-        // Added for b/243688989 flake to determine if we really are in allApps or not at this point
-        mLauncher.getAllApps();
         waitForLauncherCondition("Work tab not setup", launcher -> {
             if (launcher.getAppsView().getContentView() instanceof AllAppsPagedView) {
                 launcher.getAppsView().getAppsStore().enableDeferUpdates(DEFER_UPDATES_TEST);
@@ -115,7 +110,6 @@
     }
 
     @Test
-    @Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/243688989
     public void workTabExists() {
         assumeTrue(mWorkProfileSetupSuccessful);
         waitForWorkTabSetup();
@@ -176,7 +170,6 @@
     }
 
     @Test
-    @Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/243688989
     public void testEdu() {
         assumeTrue(mWorkProfileSetupSuccessful);
         waitForWorkTabSetup();
diff --git a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
index 9dca24b..64ab206 100644
--- a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
@@ -16,7 +16,6 @@
 package com.android.launcher3.ui.widget;
 
 import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName;
-
 import static org.junit.Assert.assertNotNull;
 
 import android.platform.test.annotations.PlatinumTest;
@@ -96,4 +95,26 @@
         mLauncher.getWorkspace().getWorkspaceAppIcon("Shortcut")
                 .launch(getAppPackageName());
     }
+
+    /**
+     * Test dragging a widget to the workspace and resize it.
+     */
+    @Test
+    public void testResizeWidget() throws Throwable {
+        new FavoriteItemsTransaction(mTargetContext).commitAndLoadHome(mLauncher);
+
+        waitForLauncherCondition("Workspace didn't finish loading", l -> !l.isWorkspaceLoading());
+
+        final LauncherAppWidgetProviderInfo widgetInfo =
+                TestViewHelpers.findWidgetProvider(false /* hasConfigureScreen */);
+
+        WidgetResizeFrame resizeFrame = mLauncher
+                .getWorkspace()
+                .openAllWidgets()
+                .getWidget(widgetInfo.getLabel(mTargetContext.getPackageManager()))
+                .dragWidgetToWorkspace();
+
+        assertNotNull("Widget resize frame not shown after widget add", resizeFrame);
+        resizeFrame.resize();
+    }
 }
diff --git a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
index a6b5369..5c753f9 100644
--- a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
@@ -48,7 +48,6 @@
 import com.android.launcher3.util.LauncherBindableItemsContainer.ItemOperator;
 import com.android.launcher3.util.Wait;
 import com.android.launcher3.util.Wait.Condition;
-import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
 import com.android.launcher3.util.rule.ShellCommandRule;
 
 import org.junit.Before;
@@ -85,7 +84,6 @@
     public void testEmpty() throws Throwable { /* needed while the broken tests are being fixed */ }
 
     @Test
-    @ScreenRecord // b/215673732
     public void testPinWidgetNoConfig() throws Throwable {
         runTest("pinWidgetNoConfig", true, (info, view) -> info instanceof LauncherAppWidgetInfo &&
                 ((LauncherAppWidgetInfo) info).appWidgetId == mAppWidgetId &&
@@ -94,7 +92,6 @@
     }
 
     @Test
-    @ScreenRecord // b/215673732
     public void testPinWidgetNoConfig_customPreview() throws Throwable {
         // Command to set custom preview
         Intent command = RequestPinItemActivity.getCommandIntent(
@@ -108,7 +105,6 @@
     }
 
     @Test
-    @ScreenRecord // b/215673732
     public void testPinWidgetWithConfig() throws Throwable {
         runTest("pinWidgetWithConfig", true,
                 (info, view) -> info instanceof LauncherAppWidgetInfo &&
diff --git a/tests/src/com/android/launcher3/ui/workspace/TwoPanelWorkspaceTest.java b/tests/src/com/android/launcher3/ui/workspace/TwoPanelWorkspaceTest.java
index 0b2f335..62a8179 100644
--- a/tests/src/com/android/launcher3/ui/workspace/TwoPanelWorkspaceTest.java
+++ b/tests/src/com/android/launcher3/ui/workspace/TwoPanelWorkspaceTest.java
@@ -178,14 +178,15 @@
     public void testDragIconToPage3() {
         Workspace workspace = mLauncher.getWorkspace();
 
-        workspace.dragIcon(workspace.getHotseatAppIcon("Phone"), 3);
+        // b/299522368 sometimes the phone app is not present in the hotseat.
+        workspace.dragIcon(workspace.getHotseatAppIcon("Chrome"), 3);
 
         executeOnLauncher(launcher -> {
             assertPagesExist(launcher, 0, 1, 2, 3);
             assertItemsOnPage(launcher, 0, "Play Store", "Maps");
             assertPageEmpty(launcher, 1);
             assertPageEmpty(launcher, 2);
-            assertItemsOnPage(launcher, 3, "Phone");
+            assertItemsOnPage(launcher, 3, "Chrome");
         });
     }
 
diff --git a/tests/src/com/android/launcher3/util/rule/TestIsolationRule.java b/tests/src/com/android/launcher3/util/rule/TestIsolationRule.java
new file mode 100644
index 0000000..5cc4c5e
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/rule/TestIsolationRule.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.util.rule;
+
+import androidx.annotation.NonNull;
+
+import com.android.launcher3.tapl.LauncherInstrumentation;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+/**
+ * Isolates tests from some of the state created by the previous test.
+ */
+public class TestIsolationRule implements TestRule {
+    final LauncherInstrumentation mLauncher;
+
+    public TestIsolationRule(LauncherInstrumentation launcher) {
+        mLauncher = launcher;
+    }
+
+    @NonNull
+    @Override
+    public Statement apply(@NonNull Statement base, @NonNull Description description) {
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                base.evaluate();
+                // Make sure that Launcher workspace looks correct.
+                mLauncher.goHome();
+            }
+        };
+    }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 1bbef36..872fe6f 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -341,6 +341,11 @@
                 .getParcelable(TestProtocol.TEST_INFO_RESPONSE_FIELD);
     }
 
+    Insets getImeInsets() {
+        return getTestInfo(TestProtocol.REQUEST_IME_INSETS)
+                .getParcelable(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+    }
+
     public boolean isTablet() {
         return getTestInfo(TestProtocol.REQUEST_IS_TABLET)
                 .getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD);
@@ -2111,7 +2116,11 @@
                         ? containerBounds.right + 1
                         : containerBounds.left - 1;
             }
-            int y = containerBounds.top + containerBounds.height() / 2;
+            // If IME is visible and overlaps the container bounds, touch above it.
+            int bottomBound = Math.min(
+                    containerBounds.bottom,
+                    getRealDisplaySize().y - getImeInsets().bottom);
+            int y = (bottomBound - containerBounds.top) / 2;
 
             final long downTime = SystemClock.uptimeMillis();
             final Point tapTarget = new Point(x, y);
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenu.java b/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenu.java
index 7c29a6c..859e504 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenu.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenu.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.tapl;
 
+import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
+
 import androidx.annotation.NonNull;
 import androidx.test.uiautomator.By;
 import androidx.test.uiautomator.UiObject2;
@@ -50,15 +52,23 @@
         }
     }
 
-    /** Taps the app info item from the overview task menu and returns the LaunchedAppState
-     * representing the App info settings page. */
+    /**
+     * Taps the app info item from the overview task menu and returns the LaunchedAppState
+     * representing the App info settings page.
+     */
     @NonNull
     public LaunchedAppState tapAppInfoMenuItem() {
         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
              LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
                      "before tapping the app info menu item")) {
-            mLauncher.clickLauncherObject(
-                    mLauncher.findObjectInContainer(mMenu, By.text("App info")));
+
+            mLauncher.executeAndWaitForLauncherEvent(
+                    () -> mLauncher.clickLauncherObject(
+                            mLauncher.findObjectInContainer(mMenu, By.text("App info"))),
+                    accessibilityEvent ->
+                            accessibilityEvent.getEventType() == TYPE_WINDOW_STATE_CHANGED,
+                    () -> "Unable to start Settings by clicking App Info",
+                    "Tap the app info menu item");
 
             try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
                     "tapped app info menu item")) {
diff --git a/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java b/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java
index 0895d93..513d6bb 100644
--- a/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java
+++ b/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java
@@ -20,6 +20,8 @@
 import androidx.test.uiautomator.By;
 import androidx.test.uiautomator.UiObject2;
 
+import com.android.launcher3.testing.shared.TestProtocol;
+
 import java.util.ArrayList;
 
 /**
@@ -43,8 +45,11 @@
     public void searchForInput(String input) {
         try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
                 "want to search for result with an input");
-             LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
-            mLauncher.waitForLauncherObject(INPUT_RES).setText(input);
+            LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
+            mLauncher.executeAndWaitForLauncherEvent(
+                    () -> mLauncher.waitForLauncherObject(INPUT_RES).setText(input),
+                    event -> TestProtocol.SEARCH_RESULT_COMPLETE.equals(event.getClassName()),
+                    () -> "Didn't receive a search result completed message", "searching");
         }
     }
 
diff --git a/tests/tapl/com/android/launcher3/tapl/WidgetResizeFrame.java b/tests/tapl/com/android/launcher3/tapl/WidgetResizeFrame.java
index 8f51d04..ec1cbd8 100644
--- a/tests/tapl/com/android/launcher3/tapl/WidgetResizeFrame.java
+++ b/tests/tapl/com/android/launcher3/tapl/WidgetResizeFrame.java
@@ -15,6 +15,16 @@
  */
 package com.android.launcher3.tapl;
 
+import static com.android.launcher3.tapl.Launchable.DEFAULT_DRAG_STEPS;
+import static org.junit.Assert.assertTrue;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.SystemClock;
+import android.view.MotionEvent;
+
+import androidx.test.uiautomator.UiObject2;
+
 /** The resize frame that is shown for a widget on the workspace. */
 public class WidgetResizeFrame {
 
@@ -34,4 +44,36 @@
             mLauncher.getDevice().pressHome();
         }
     }
+
+    /** Resizes the widget to double its height, and returns the resize frame. */
+    public WidgetResizeFrame resize() {
+        try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+             LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                     "want to resize the widget frame.")) {
+            UiObject2 widget = mLauncher.waitForLauncherObject("widget_resize_frame");
+            UiObject2 bottomResizeHandle =
+                    mLauncher.waitForLauncherObject("widget_resize_bottom_handle");
+            Rect originalWidgetSize = widget.getVisibleBounds();
+            Point targetStart = bottomResizeHandle.getVisibleCenter();
+            Point targetDest = bottomResizeHandle.getVisibleCenter();
+            targetDest.offset(0, originalWidgetSize.height());
+
+            final long downTime = SystemClock.uptimeMillis();
+            mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, targetStart,
+                    LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER);
+            mLauncher.movePointer(targetStart, targetDest, DEFAULT_DRAG_STEPS,
+                    true, downTime, downTime, true,
+                    LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER);
+            mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_UP, targetDest,
+                    LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER);
+
+            try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer(
+                         "want to return resized widget resize frame")) {
+                float newHeight = mLauncher.waitForLauncherObject(
+                        "widget_resize_frame").getVisibleBounds().height();
+                assertTrue("Widget not resized.", newHeight >= originalWidgetSize.height() * 2);
+                return this;
+            }
+        }
+    }
 }