Merge "Cleaning up some enabled features" into main
diff --git a/aconfig/Android.bp b/aconfig/Android.bp
index bca7494..5413601 100644
--- a/aconfig/Android.bp
+++ b/aconfig/Android.bp
@@ -20,7 +20,7 @@
 aconfig_declarations {
     name: "com_android_launcher3_flags",
     package: "com.android.launcher3",
-    container: "system_ext",
+    container: "system",
     srcs: ["**/*.aconfig"],
 }
 
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index 3769124..43b92fa 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -1,5 +1,5 @@
 package: "com.android.launcher3"
-container: "system_ext"
+container: "system"
 
 flag {
     name: "enable_expanding_pause_work_button"
@@ -398,3 +398,17 @@
       purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    name: "enable_dismiss_prediction_undo"
+    namespace: "launcher"
+    description: "Show an 'Undo' snackbar when users dismiss a predicted hotseat item"
+    bug: "270394476"
+}
+
+flag {
+    name: "enable_all_apps_button_in_hotseat"
+    namespace: "launcher"
+    description: "Enables displaying the all apps button in the hotseat."
+    bug: "270393897"
+}
diff --git a/aconfig/launcher_overview.aconfig b/aconfig/launcher_overview.aconfig
index 23733a4..853faf8 100644
--- a/aconfig/launcher_overview.aconfig
+++ b/aconfig/launcher_overview.aconfig
@@ -1,5 +1,5 @@
 package: "com.android.launcher3"
-container: "system_ext"
+container: "system"
 
 flag {
     name: "enable_grid_only_overview"
diff --git a/aconfig/launcher_search.aconfig b/aconfig/launcher_search.aconfig
index b98eee6..72f654e 100644
--- a/aconfig/launcher_search.aconfig
+++ b/aconfig/launcher_search.aconfig
@@ -1,5 +1,5 @@
 package: "com.android.launcher3"
-container: "system_ext"
+container: "system"
 
 flag {
     name: "enable_private_space"
diff --git a/quickstep/res/values-nb/strings.xml b/quickstep/res/values-nb/strings.xml
index 4b982ef..21b5fd4 100644
--- a/quickstep/res/values-nb/strings.xml
+++ b/quickstep/res/values-nb/strings.xml
@@ -97,7 +97,7 @@
     <string name="action_share" msgid="2648470652637092375">"Del"</string>
     <string name="action_screenshot" msgid="8171125848358142917">"Skjermbilde"</string>
     <string name="action_split" msgid="2098009717623550676">"Del opp"</string>
-    <string name="action_save_app_pair" msgid="5974823919237645229">"Lagre apptilkobling"</string>
+    <string name="action_save_app_pair" msgid="5974823919237645229">"Lagre app-paret"</string>
     <string name="toast_split_select_app" msgid="8464310533320556058">"Trykk på en annen app for å bruke delt skjerm"</string>
     <string name="toast_contextual_split_select_app" msgid="433510957123687090">"Velg en annen app for å bruke delt skjerm"</string>
     <string name="toast_split_select_app_cancel" msgid="1939025102486630426">"Avbryt"</string>
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 5513599..c29e60e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -135,11 +135,6 @@
         mHomeState.removeListener(mVisibilityChangeListener);
     }
 
-    /** Returns {@code true} if launcher is currently presenting the home screen. */
-    public boolean isOnHome() {
-        return mTaskbarLauncherStateController.isOnHome();
-    }
-
     private void onInAppDisplayProgressChanged() {
         if (mControllers != null) {
             // Update our shared state so we can restore it if taskbar gets recreated.
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index a979d58..e9bd30a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -91,6 +91,7 @@
 import com.android.launcher3.anim.AlphaUpdateListener;
 import com.android.launcher3.anim.AnimatedFloat;
 import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarButton;
+import com.android.launcher3.taskbar.bubbles.BubbleBarController;
 import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory;
 import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory.NavButtonLayoutter;
 import com.android.launcher3.taskbar.navbutton.NearestTouchFrame;
@@ -106,6 +107,7 @@
 import com.android.systemui.shared.statusbar.phone.BarTransitions;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -115,7 +117,8 @@
 /**
  * Controller for managing nav bar buttons in taskbar
  */
-public class NavbarButtonsViewController implements TaskbarControllers.LoggableTaskbarController {
+public class NavbarButtonsViewController implements TaskbarControllers.LoggableTaskbarController,
+        BubbleBarController.BubbleBarLocationListener {
 
     private final Rect mTempRect = new Rect();
 
@@ -397,6 +400,12 @@
             }
         };
         mSeparateWindowParent.recreateControllers();
+        if (com.android.wm.shell.Flags.enableBubbleBarInPersistentTaskBar()
+                && mControllers.bubbleControllers.isPresent()) {
+            BubbleBarLocation bubblesLocation = mControllers.bubbleControllers.get()
+                    .bubbleBarViewController.getBubbleBarLocation();
+            onBubbleBarLocationUpdated(bubblesLocation);
+        }
     }
 
     private void initButtons(ViewGroup navContainer, ViewGroup endContainer,
@@ -1168,6 +1177,52 @@
         mHitboxExtender.onAnimationProgressToOverview(alignment);
     }
 
+    /** Adjusts navigation buttons layout accordingly to the bubble bar position. */
+    @Override
+    public void onBubbleBarLocationUpdated(BubbleBarLocation location) {
+        mNavButtonContainer.setTranslationX(getNavBarTranslationX(location));
+    }
+
+    /** Animates navigation buttons accordingly to the bubble bar position. */
+    @Override
+    public void onBubbleBarLocationAnimated(BubbleBarLocation location) {
+        // TODO(b/346381754) add the teleport animation similarly to the bubble bar
+        mNavButtonContainer.setTranslationX(getNavBarTranslationX(location));
+    }
+
+    private int getNavBarTranslationX(BubbleBarLocation location) {
+        boolean isNavbarOnRight = location.isOnLeft(mNavButtonsView.isLayoutRtl());
+        DeviceProfile dp = mContext.getDeviceProfile();
+        float navBarTargetStartX;
+        if (mContext.shouldStartAlignTaskbar()) {
+            int navBarSpacing = dp.inlineNavButtonsEndSpacingPx;
+            // If the taskbar is start aligned the navigation bar is aligned to the start or end of
+            // the container, depending on the bubble bar location
+            if (isNavbarOnRight) {
+                navBarTargetStartX = dp.widthPx - navBarSpacing - mNavButtonContainer.getWidth();
+            } else {
+                navBarTargetStartX = navBarSpacing;
+            }
+        } else {
+            // If the task bar is not start aligned, the navigation bar is located in the center
+            // between the taskbar and screen edges, depending on the bubble bar location.
+            float navbarWidth = mNavButtonContainer.getWidth();
+            Rect taskbarBounds = mControllers.taskbarViewController.getIconLayoutBounds();
+            if (isNavbarOnRight) {
+                if (mNavButtonsView.isLayoutRtl()) {
+                    float taskBarEnd = taskbarBounds.right;
+                    navBarTargetStartX = (dp.widthPx + taskBarEnd - navbarWidth) / 2;
+                } else {
+                    navBarTargetStartX = mNavButtonContainer.getLeft();
+                }
+            } else {
+                float taskBarStart = taskbarBounds.left;
+                navBarTargetStartX = (taskBarStart - navbarWidth) / 2;
+            }
+        }
+        return (int) navBarTargetStartX - mNavButtonContainer.getLeft();
+    }
+
     private class RotationButtonListener implements RotationButton.RotationButtonUpdatesCallback {
         @Override
         public void onVisibilityChanged(boolean isVisible) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 0efa949..901b646 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -37,6 +37,7 @@
 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;
+import static com.android.launcher3.taskbar.TaskbarStashController.SHOULD_BUBBLES_FOLLOW_DEFAULT_VALUE;
 import static com.android.launcher3.testing.shared.ResourceUtils.getBoolByName;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.quickstep.util.AnimUtils.completeRunnableListCallback;
@@ -687,6 +688,11 @@
         return mNavMode == NavigationMode.THREE_BUTTONS;
     }
 
+    /** Returns whether taskbar should start align. */
+    public boolean shouldStartAlignTaskbar() {
+        return isThreeButtonNav() && mDeviceProfile.startAlignTaskbar;
+    }
+
     public boolean isGestureNav() {
         return mNavMode == NavigationMode.NO_BUTTON;
     }
@@ -1550,10 +1556,12 @@
 
     /**
      * Called when we want to unstash taskbar when user performs swipes up gesture.
+     * @param delayTaskbarBackground whether we will delay the taskbar background animation
      */
-    public void onSwipeToUnstashTaskbar() {
+    public void onSwipeToUnstashTaskbar(boolean delayTaskbarBackground) {
         boolean wasStashed = mControllers.taskbarStashController.isStashed();
-        mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(/* stash= */ false);
+        mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(/* stash= */ false,
+                SHOULD_BUBBLES_FOLLOW_DEFAULT_VALUE, delayTaskbarBackground);
         boolean isStashed = mControllers.taskbarStashController.isStashed();
         if (isStashed != wasStashed) {
             VibratorWrapper.INSTANCE.get(this).vibrateForTaskbarUnstash();
@@ -1687,7 +1695,7 @@
                 duration);
 
         View allAppsButton = mControllers.taskbarViewController.getAllAppsButtonView();
-        if (allAppsButton != null && !FeatureFlags.ENABLE_ALL_APPS_BUTTON_IN_HOTSEAT.get()) {
+        if (allAppsButton != null && !FeatureFlags.enableAllAppsButtonInHotseat()) {
             ValueAnimator alphaOverride = ValueAnimator.ofFloat(0, 1);
             alphaOverride.setDuration(duration);
             alphaOverride.addUpdateListener(a -> {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index 6c6bd71..ab3b44e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -16,6 +16,7 @@
 package com.android.launcher3.taskbar;
 
 import static com.android.app.animation.Interpolators.EMPHASIZED;
+import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
 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;
@@ -440,11 +441,6 @@
         return animator;
     }
 
-    /** Returns {@code true} if launcher is currently presenting the home screen. */
-    public boolean isOnHome() {
-        return isInLauncher() && mLauncherState == LauncherState.NORMAL;
-    }
-
     private Animator onStateChangeApplied(int changedFlags, long duration, boolean start) {
         final boolean isInLauncher = isInLauncher();
         final boolean isIconAlignedWithHotseat = isIconAlignedWithHotseat();
@@ -456,9 +452,11 @@
                     + ", toAlignment: " + toAlignment);
         }
         mControllers.bubbleControllers.ifPresent(controllers -> {
-            // Show the bubble bar when on launcher home or in overview.
+            // Show the bubble bar when on launcher home (hotseat icons visible) or in overview
             boolean onOverview = mLauncherState == LauncherState.OVERVIEW;
-            controllers.bubbleStashController.setBubblesShowingOnHome(isOnHome());
+            boolean hotseatIconsVisible = isInLauncher && mLauncherState.areElementsVisible(
+                    mLauncher, HOTSEAT_ICONS);
+            controllers.bubbleStashController.setBubblesShowingOnHome(hotseatIconsVisible);
             controllers.bubbleStashController.setBubblesShowingOnOverview(onOverview);
         });
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index c1ed39a..2c2f65d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -82,6 +82,11 @@
     private static final String TAG = "TaskbarStashController";
     private static final boolean DEBUG = false;
 
+    /**
+     * Def. value for @param shouldBubblesFollow in
+     * {@link #updateAndAnimateTransientTaskbar(boolean)} */
+    public static boolean SHOULD_BUBBLES_FOLLOW_DEFAULT_VALUE = true;
+
     public static final int FLAG_IN_APP = 1 << 0;
     public static final int FLAG_STASHED_IN_APP_SYSUI = 1 << 1; // shade open, ...
     public static final int FLAG_STASHED_IN_APP_SETUP = 1 << 2; // setup wizard and AllSetActivity
@@ -94,6 +99,8 @@
     public static final int FLAG_STASHED_SYSUI = 1 << 9; //  app pinning,...
     public static final int FLAG_STASHED_DEVICE_LOCKED = 1 << 10; // device is locked: keyguard, ...
     public static final int FLAG_IN_OVERVIEW = 1 << 11; // launcher is in overview
+    // An internal no-op flag to determine whether we should delay the taskbar background animation
+    private static final int FLAG_DELAY_TASKBAR_BG_TAG = 1 << 12;
 
     // If any of these flags are enabled, isInApp should return true.
     private static final int FLAGS_IN_APP = FLAG_IN_APP | FLAG_IN_SETUP;
@@ -489,9 +496,17 @@
     /**
      * Stash or unstashes the transient taskbar, using the default TASKBAR_STASH_DURATION.
      * If bubble bar exists, it will match taskbars stashing behavior.
+     * Will not delay taskbar background by default.
      */
     public void updateAndAnimateTransientTaskbar(boolean stash) {
-        updateAndAnimateTransientTaskbar(stash, /* shouldBubblesFollow= */ true);
+        updateAndAnimateTransientTaskbar(stash, SHOULD_BUBBLES_FOLLOW_DEFAULT_VALUE, false);
+    }
+
+    /**
+     * Stash or unstashes the transient taskbar, using the default TASKBAR_STASH_DURATION.
+     */
+    public void updateAndAnimateTransientTaskbar(boolean stash, boolean shouldBubblesFollow) {
+        updateAndAnimateTransientTaskbar(stash, shouldBubblesFollow, false);
     }
 
     /**
@@ -499,28 +514,47 @@
      *
      * @param stash               whether transient taskbar should be stashed.
      * @param shouldBubblesFollow whether bubbles should match taskbars behavior.
+     * @param delayTaskbarBackground whether we will delay the taskbar background animation
      */
-    public void updateAndAnimateTransientTaskbar(boolean stash, boolean shouldBubblesFollow) {
+    public void updateAndAnimateTransientTaskbar(boolean stash, boolean shouldBubblesFollow,
+            boolean delayTaskbarBackground) {
         if (!DisplayController.isTransientTaskbar(mActivity)) {
             return;
         }
 
-        if (
-                stash
-                        && !mControllers.taskbarAutohideSuspendController
-                        .isSuspendedForTransientTaskbarInLauncher()
-                        && mControllers.taskbarAutohideSuspendController
-                        .isTransientTaskbarStashingSuspended()) {
+        if (stash
+                && !mControllers.taskbarAutohideSuspendController
+                .isSuspendedForTransientTaskbarInLauncher()
+                && mControllers.taskbarAutohideSuspendController
+                .isTransientTaskbarStashingSuspended()) {
             // Avoid stashing if autohide is currently suspended.
             return;
         }
 
+        boolean shouldApplyState = false;
+
+        if (delayTaskbarBackground) {
+            mControllers.taskbarStashController.updateStateForFlag(FLAG_DELAY_TASKBAR_BG_TAG, true);
+            shouldApplyState = true;
+        }
+
         if (hasAnyFlag(FLAG_STASHED_IN_APP_AUTO) != stash) {
             mTaskbarSharedState.taskbarWasStashedAuto = stash;
             updateStateForFlag(FLAG_STASHED_IN_APP_AUTO, stash);
+            shouldApplyState = true;
+        }
+
+        if (shouldApplyState) {
             applyState();
         }
 
+        // Effectively a no-opp to remove the tag.
+        if (delayTaskbarBackground) {
+            mControllers.taskbarStashController.updateStateForFlag(FLAG_DELAY_TASKBAR_BG_TAG,
+                    false);
+            mControllers.taskbarStashController.applyState(0);
+        }
+
         mControllers.bubbleControllers.ifPresent(controllers -> {
             if (shouldBubblesFollow) {
                 final boolean willStash = mIsStashedPredicate.test(mState);
@@ -584,14 +618,14 @@
     /**
      * Create a stash animation and save to {@link #mAnimator}.
      *
-     * @param isStashed                  whether it's a stash animation or an unstash animation
-     * @param duration                   duration of the animation
-     * @param animationType              what transition type to play.
-     * @param skipTaskbarBackgroundDelay Iff true, skips delaying the taskbar background.
-     * @param jankTag                    tag to be used in jank monitor trace.
+     * @param isStashed             whether it's a stash animation or an unstash animation
+     * @param duration              duration of the animation
+     * @param animationType         what transition type to play.
+     * @param shouldDelayBackground whether we should delay the taskbar bg animation
+     * @param jankTag               tag to be used in jank monitor trace.
      */
     private void createAnimToIsStashed(boolean isStashed, long duration,
-            @StashAnimation int animationType, boolean skipTaskbarBackgroundDelay, String jankTag) {
+            @StashAnimation int animationType, boolean shouldDelayBackground, String jankTag) {
         if (animationType == TRANSITION_UNSTASH_SUW_MANUAL && isStashed) {
             // The STASH_ANIMATION_SUW_MANUAL must only be used during an unstash animation.
             Log.e(TAG, "Illegal arguments:Using TRANSITION_UNSTASH_SUW_MANUAL to stash taskbar");
@@ -630,7 +664,7 @@
 
         if (isTransientTaskbar) {
             createTransientAnimToIsStashed(mAnimator, isStashed, duration,
-                    skipTaskbarBackgroundDelay, animationType);
+                    shouldDelayBackground, animationType);
         } else {
             createAnimToIsStashed(mAnimator, isStashed, duration, stashTranslation, animationType);
         }
@@ -736,7 +770,7 @@
     }
 
     private void createTransientAnimToIsStashed(AnimatorSet as, boolean isStashed, long duration,
-            boolean skipTaskbarBackgroundDelay, @StashAnimation int animationType) {
+            boolean shouldDelayBackground, @StashAnimation int animationType) {
         // Target values of the properties this is going to set
         final float backgroundOffsetTarget = isStashed ? 1 : 0;
         final float iconAlphaTarget = isStashed ? 0 : 1;
@@ -787,7 +821,10 @@
                 backgroundAndHandleAlphaStartDelay,
                 backgroundAndHandleAlphaDuration, LINEAR);
 
-        if (enableScalingRevealHomeAnimation() && isStashed && !skipTaskbarBackgroundDelay) {
+
+        if (enableScalingRevealHomeAnimation()
+                && !isStashed
+                && shouldDelayBackground) {
             play(as, getTaskbarBackgroundAnimatorWhenNotGoingHome(duration),
                     0, 0, LINEAR);
             as.addListener(AnimatorListeners.forEndCallback(
@@ -1349,10 +1386,9 @@
                 mIsStashed = isStashed;
                 mLastStartedTransitionType = animationType;
 
-                boolean skipTaskbarBgDelay = !hasAnyFlag(FLAG_STASHED_IN_TASKBAR_ALL_APPS)
-                        && hasAnyFlag(FLAG_STASHED_IN_TASKBAR_ALL_APPS, changedFlags);
+                boolean shouldDelayBackground = hasAnyFlag(FLAG_DELAY_TASKBAR_BG_TAG);
                 // This sets mAnimator.
-                createAnimToIsStashed(mIsStashed, duration, animationType, skipTaskbarBgDelay,
+                createAnimToIsStashed(mIsStashed, duration, animationType, shouldDelayBackground,
                         computeTaskbarJankMonitorTag(changedFlags));
                 return mAnimator;
             }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index 32d6561..0389a11 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -87,6 +87,7 @@
     private final boolean mIsRtl;
 
     private final TaskbarActivityContext mActivityContext;
+    @Nullable private BubbleBarLocation mBubbleBarLocation = null;
 
     // Initialized in init.
     private TaskbarViewCallbacks mControllerCallbacks;
@@ -197,7 +198,7 @@
 
     @Override
     public void onDeviceProfileChanged(DeviceProfile dp) {
-        mShouldTryStartAlign = mActivityContext.isThreeButtonNav() && dp.startAlignTaskbar;
+        mShouldTryStartAlign = mActivityContext.shouldStartAlignTaskbar();
     }
 
     @Override
@@ -494,39 +495,60 @@
         icon.setOnHoverListener(mControllerCallbacks.getIconOnHoverListener(icon));
     }
 
+    /** Updates taskbar icons accordingly to the new bubble bar location. */
+    public void onBubbleBarLocationUpdated(BubbleBarLocation location) {
+        if (mBubbleBarLocation == location) return;
+        mBubbleBarLocation = location;
+        requestLayout();
+    }
+
+    /**
+     * Returns translation X for the taskbar icons for provided {@link BubbleBarLocation}. If the
+     * bubble bar is not enabled, or location of the bubble bar is the same, or taskbar is not start
+     * aligned - returns 0.
+     */
+    public float getTranslationXForBubbleBarPosition(BubbleBarLocation location) {
+        if (!mControllerCallbacks.isBubbleBarEnabledInPersistentTaskbar()
+                || location == mBubbleBarLocation
+                || !mActivityContext.shouldStartAlignTaskbar()
+        ) {
+            return 0;
+        }
+        Rect iconsBounds = getIconLayoutBounds();
+        return getTaskBarIconsEndForBubbleBarLocation(location) - iconsBounds.right;
+    }
+
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        int count = getChildCount();
-        DeviceProfile deviceProfile = mActivityContext.getDeviceProfile();
         int spaceNeeded = getIconLayoutWidth();
-        int navSpaceNeeded = deviceProfile.hotseatBarEndOffset;
         boolean layoutRtl = isLayoutRtl();
-        int centerAlignIconEnd = right - (right - left - spaceNeeded) / 2;
-        int iconEnd;
-
+        DeviceProfile deviceProfile = mActivityContext.getDeviceProfile();
+        int navSpaceNeeded = deviceProfile.hotseatBarEndOffset;
+        int centerAlignIconEnd = (right + left + spaceNeeded) / 2;
+        int iconEnd = centerAlignIconEnd;
         if (mShouldTryStartAlign) {
-            // Taskbar is aligned to the start
             int startSpacingPx = deviceProfile.inlineNavButtonsEndSpacingPx;
-
-            if (layoutRtl) {
-                iconEnd = right - startSpacingPx;
+            if (mControllerCallbacks.isBubbleBarEnabledInPersistentTaskbar()
+                    && mBubbleBarLocation != null
+                    && mActivityContext.shouldStartAlignTaskbar()) {
+                iconEnd = (int) getTaskBarIconsEndForBubbleBarLocation(mBubbleBarLocation);
             } else {
-                iconEnd = startSpacingPx + spaceNeeded;
+                if (layoutRtl) {
+                    iconEnd = right - startSpacingPx;
+                } else {
+                    iconEnd = startSpacingPx + spaceNeeded;
+                }
+                boolean needMoreSpaceForNav = layoutRtl
+                        ? navSpaceNeeded > (iconEnd - spaceNeeded)
+                        : iconEnd > (right - navSpaceNeeded);
+                if (needMoreSpaceForNav) {
+                    // Add offset to account for nav bar when taskbar is centered
+                    int offset = layoutRtl
+                            ? navSpaceNeeded - (centerAlignIconEnd - spaceNeeded)
+                            : (right - navSpaceNeeded) - centerAlignIconEnd;
+                    iconEnd = centerAlignIconEnd + offset;
+                }
             }
-        } else {
-            iconEnd = centerAlignIconEnd;
-        }
-
-        boolean needMoreSpaceForNav = layoutRtl
-                ? navSpaceNeeded > (iconEnd - spaceNeeded)
-                : iconEnd > (right - navSpaceNeeded);
-        if (needMoreSpaceForNav) {
-            // Add offset to account for nav bar when taskbar is centered
-            int offset = layoutRtl
-                    ? navSpaceNeeded - (centerAlignIconEnd - spaceNeeded)
-                    : (right - navSpaceNeeded) - centerAlignIconEnd;
-
-            iconEnd = centerAlignIconEnd + offset;
         }
 
         // Currently, we support only one device with display cutout and we only are concern about
@@ -558,6 +580,7 @@
         mIconLayoutBounds.right = iconEnd;
         mIconLayoutBounds.top = (bottom - top - mIconTouchSize) / 2;
         mIconLayoutBounds.bottom = mIconLayoutBounds.top + mIconTouchSize;
+        int count = getChildCount();
         for (int i = count; i > 0; i--) {
             View child = getChildAt(i - 1);
             if (child == mQsb) {
@@ -770,4 +793,19 @@
         }
         return mAllAppsButtonContainer;
     }
+
+    /**
+     * This method only works for bubble bar enabled in persistent task bar and the taskbar is start
+     * aligned.
+     */
+    private float getTaskBarIconsEndForBubbleBarLocation(BubbleBarLocation location) {
+        DeviceProfile deviceProfile = mActivityContext.getDeviceProfile();
+        boolean navbarOnRight = location.isOnLeft(isLayoutRtl());
+        int navSpaceNeeded = deviceProfile.hotseatBarEndOffset;
+        if (navbarOnRight) {
+            return getWidth() - navSpaceNeeded;
+        } else {
+            return navSpaceNeeded + getIconLayoutWidth();
+        }
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java
index 5ec00ac..5c8d439 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java
@@ -28,6 +28,7 @@
 import com.android.internal.jank.Cuj;
 import com.android.launcher3.taskbar.bubbles.BubbleBarViewController;
 import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
+import com.android.wm.shell.Flags;
 import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
 
 /**
@@ -127,4 +128,10 @@
         }
         return null;
     }
+
+    /** Returns true if bubble bar controllers present and enabled in persistent taskbar. */
+    public boolean isBubbleBarEnabledInPersistentTaskbar() {
+        return Flags.enableBubbleBarInPersistentTaskBar()
+                && mControllers.bubbleControllers.isPresent();
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 292b9ed..b663ccb 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -29,7 +29,10 @@
 import static com.android.launcher3.config.FeatureFlags.enableTaskbarPinning;
 import static com.android.launcher3.taskbar.TaskbarPinningController.PINNING_PERSISTENT;
 import static com.android.launcher3.taskbar.TaskbarPinningController.PINNING_TRANSIENT;
+import static com.android.launcher3.taskbar.bubbles.BubbleBarView.FADE_IN_ANIM_ALPHA_DURATION_MS;
+import static com.android.launcher3.taskbar.bubbles.BubbleBarView.FADE_OUT_ANIM_POSITION_DURATION_MS;
 import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
+import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_NAV_BAR_ANIM;
 import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_TASKBAR_ALIGNMENT_ANIM;
 import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_TASKBAR_PINNING_ANIM;
 import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_TASKBAR_REVEAL_ANIM;
@@ -66,6 +69,7 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.TaskItemInfo;
+import com.android.launcher3.taskbar.bubbles.BubbleBarController;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.LauncherBindableItemsContainer;
@@ -74,6 +78,8 @@
 import com.android.launcher3.util.MultiValueAlpha;
 import com.android.quickstep.util.GroupTask;
 import com.android.systemui.shared.recents.model.Task;
+import com.android.wm.shell.Flags;
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
 
 import java.io.PrintWriter;
 import java.util.Set;
@@ -82,7 +88,8 @@
 /**
  * Handles properties/data collection, then passes the results to TaskbarView to render.
  */
-public class TaskbarViewController implements TaskbarControllers.LoggableTaskbarController {
+public class TaskbarViewController implements TaskbarControllers.LoggableTaskbarController,
+        BubbleBarController.BubbleBarLocationListener {
 
     private static final String TAG = "TaskbarViewController";
 
@@ -122,6 +129,14 @@
     private final AnimatedFloat mTaskbarIconTranslationXForPinning = new AnimatedFloat(
             this::updateTaskbarIconTranslationXForPinning);
 
+    private final AnimatedFloat mIconsTranslationXForNavbar = new AnimatedFloat(
+            this::updateTranslationXForNavBar);
+
+    @Nullable
+    private Animator mTaskbarShiftXAnim;
+    @Nullable
+    private BubbleBarLocation mCurrentBubbleBarLocation;
+
     private final AnimatedFloat mTaskbarIconTranslationYForPinning = new AnimatedFloat(
             this::updateTranslationY);
 
@@ -227,6 +242,54 @@
         }
     }
 
+    /** Adjusts start aligned taskbar layout accordingly to the bubble bar position. */
+    @Override
+    public void onBubbleBarLocationUpdated(BubbleBarLocation location) {
+        updateCurrentBubbleBarLocation(location);
+        if (!shouldMoveTaskbarOnBubbleBarLocationUpdate()) return;
+        cancelTaskbarShiftAnimation();
+        // reset translation x, taskbar will position icons with the updated location
+        mIconsTranslationXForNavbar.updateValue(0);
+        mTaskbarView.onBubbleBarLocationUpdated(location);
+    }
+
+    /** Animates start aligned taskbar accordingly to the bubble bar position. */
+    @Override
+    public void onBubbleBarLocationAnimated(BubbleBarLocation location) {
+        if (!updateCurrentBubbleBarLocation(location)
+                || !shouldMoveTaskbarOnBubbleBarLocationUpdate()) {
+            return;
+        }
+        cancelTaskbarShiftAnimation();
+        float translationX = mTaskbarView.getTranslationXForBubbleBarPosition(location);
+        mTaskbarShiftXAnim = createTaskbarIconsShiftAnimator(translationX);
+        mTaskbarShiftXAnim.start();
+    }
+
+    /** Updates the mCurrentBubbleBarLocation, returns {@code} true if location is updated. */
+    private boolean updateCurrentBubbleBarLocation(BubbleBarLocation location) {
+        if (mCurrentBubbleBarLocation == location || location == null) {
+            return false;
+        } else {
+            mCurrentBubbleBarLocation = location;
+            return true;
+        }
+    }
+
+    /** Returns whether taskbar should be moved on the bubble bar location update. */
+    private boolean shouldMoveTaskbarOnBubbleBarLocationUpdate() {
+        return Flags.enableBubbleBarInPersistentTaskBar()
+                && mControllers.bubbleControllers.isPresent()
+                && mActivity.shouldStartAlignTaskbar()
+                && mActivity.isThreeButtonNav();
+    }
+
+    private void cancelTaskbarShiftAnimation() {
+        if (mTaskbarShiftXAnim != null) {
+            mTaskbarShiftXAnim.cancel();
+        }
+    }
+
     /**
      * Announcement for Accessibility when Taskbar stashes/unstashes.
      */
@@ -460,6 +523,17 @@
                 + mTaskbarIconTranslationYForSpringOnStash);
     }
 
+    private void updateTranslationXForNavBar() {
+        View[] iconViews = mTaskbarView.getIconViews();
+        float translationX = mIconsTranslationXForNavbar.value;
+        for (int iconIndex = 0; iconIndex < iconViews.length; iconIndex++) {
+            View iconView = iconViews[iconIndex];
+            MultiTranslateDelegate translateDelegate =
+                    ((Reorderable) iconView).getTranslateDelegate();
+            translateDelegate.getTranslationX(INDEX_NAV_BAR_ANIM).setValue(translationX);
+        }
+    }
+
     /**
      * Computes translation y for taskbar pinning.
      */
@@ -763,7 +837,7 @@
                 // plays iconAlignment to 1 really fast, therefore moving the fading towards the end
                 // to avoid icons disappearing rather than fading out visually.
                 setter.setViewAlpha(child, 0, Interpolators.clampToProgress(LINEAR, 0.8f, 1f));
-            } else if ((isAllAppsButton && !FeatureFlags.ENABLE_ALL_APPS_BUTTON_IN_HOTSEAT.get())
+            } else if ((isAllAppsButton && !FeatureFlags.enableAllAppsButtonInHotseat())
                     || (isTaskbarDividerView && enableTaskbarPinning())
                     || (isRecentTask && !isRecentsInHotseat)) {
                 if (!isToHome
@@ -1018,4 +1092,12 @@
     public static void enableModelLoadingForTests(boolean enable) {
         sEnableModelLoadingForTests = enable;
     }
+
+    private ObjectAnimator createTaskbarIconsShiftAnimator(float translationX) {
+        ObjectAnimator animator = mIconsTranslationXForNavbar.animateToValue(translationX);
+        animator.setStartDelay(FADE_OUT_ANIM_POSITION_DURATION_MS);
+        animator.setDuration(FADE_IN_ANIM_ALPHA_DURATION_MS);
+        animator.setInterpolator(Interpolators.EMPHASIZED);
+        return animator;
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
index d70a317..6860004 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
@@ -118,6 +118,7 @@
     private Optional<BubbleStashedHandleViewController> mBubbleStashedHandleViewController;
     private BubblePinController mBubblePinController;
     private BubbleCreator mBubbleCreator;
+    private BubbleBarLocationListener mBubbleBarLocationListener;
 
     // Cache last sent top coordinate to avoid sending duplicate updates to shell
     private int mLastSentBubbleBarTop;
@@ -176,6 +177,7 @@
 
     /** Initializes controllers. */
     public void init(BubbleControllers bubbleControllers,
+            BubbleBarLocationListener bubbleBarLocationListener,
             ImeVisibilityChecker imeVisibilityChecker) {
         mImeVisibilityChecker = imeVisibilityChecker;
         mBubbleBarViewController = bubbleControllers.bubbleBarViewController;
@@ -183,6 +185,7 @@
         mBubbleStashedHandleViewController = bubbleControllers.bubbleStashedHandleViewController;
         mBubblePinController = bubbleControllers.bubblePinController;
         mBubbleCreator = bubbleControllers.bubbleCreator;
+        mBubbleBarLocationListener = bubbleBarLocationListener;
 
         bubbleControllers.runAfterInit(() -> {
             mBubbleBarViewController.setHiddenForBubbles(
@@ -488,12 +491,16 @@
     private void updateBubbleBarLocationInternal(BubbleBarLocation location) {
         mBubbleBarViewController.setBubbleBarLocation(location);
         mBubbleStashController.setBubbleBarLocation(location);
+        mBubbleBarLocationListener.onBubbleBarLocationUpdated(location);
     }
 
     @Override
     public void animateBubbleBarLocation(BubbleBarLocation bubbleBarLocation) {
         MAIN_EXECUTOR.execute(
-                () -> mBubbleBarViewController.animateBubbleBarLocation(bubbleBarLocation));
+                () -> {
+                    mBubbleBarViewController.animateBubbleBarLocation(bubbleBarLocation);
+                    mBubbleBarLocationListener.onBubbleBarLocationAnimated(bubbleBarLocation);
+                });
     }
 
     /** Notifies WMShell to show the expanded view. */
@@ -518,4 +525,14 @@
         /** Whether the IME is visible. */
         boolean isImeVisible();
     }
+
+    /** Listener of {@link BubbleBarLocation} updates. */
+    public interface BubbleBarLocationListener {
+
+        /** Called when {@link BubbleBarLocation} is animated, but change is not yet final. */
+        void onBubbleBarLocationAnimated(BubbleBarLocation location);
+
+        /** Called when {@link BubbleBarLocation} is updated permanently. */
+        void onBubbleBarLocationUpdated(BubbleBarLocation location);
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarLocationCompositeListener.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarLocationCompositeListener.kt
new file mode 100644
index 0000000..8e176ac
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarLocationCompositeListener.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 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.taskbar.bubbles
+
+import com.android.launcher3.taskbar.bubbles.BubbleBarController.BubbleBarLocationListener
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation
+
+/** Composite implementation of [BubbleBarLocationListener] interface */
+class BubbleBarLocationCompositeListener(private val listeners: List<BubbleBarLocationListener>) :
+    BubbleBarLocationListener {
+
+    constructor(vararg listeners: BubbleBarLocationListener) : this(listOf(*listeners))
+
+    override fun onBubbleBarLocationAnimated(location: BubbleBarLocation?) {
+        listeners.forEach { it.onBubbleBarLocationAnimated(location) }
+    }
+
+    override fun onBubbleBarLocationUpdated(location: BubbleBarLocation?) {
+        listeners.forEach { it.onBubbleBarLocationUpdated(location) }
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarPinController.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarPinController.kt
index 9c34307..a34fab2 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarPinController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarPinController.kt
@@ -27,6 +27,7 @@
 import android.widget.FrameLayout
 import androidx.core.view.updateLayoutParams
 import com.android.launcher3.R
+import com.android.launcher3.taskbar.bubbles.BubbleBarController.BubbleBarLocationListener
 import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController
 import com.android.wm.shell.shared.bubbles.BaseBubblePinController
 import com.android.wm.shell.shared.bubbles.BubbleBarLocation
@@ -42,12 +43,17 @@
 
     private lateinit var bubbleBarViewController: BubbleBarViewController
     private lateinit var bubbleStashController: BubbleStashController
+    private lateinit var bubbleBarLocationListener: BubbleBarLocationListener
     private var exclRectWidth: Float = 0f
     private var exclRectHeight: Float = 0f
 
     private var dropTargetView: View? = null
 
-    fun init(bubbleControllers: BubbleControllers) {
+    fun init(
+        bubbleControllers: BubbleControllers,
+        bubbleBarLocationListener: BubbleBarLocationListener
+    ) {
+        this.bubbleBarLocationListener = bubbleBarLocationListener
         bubbleBarViewController = bubbleControllers.bubbleBarViewController
         bubbleStashController = bubbleControllers.bubbleStashController
         exclRectWidth = context.resources.getDimension(R.dimen.bubblebar_dismiss_zone_width)
@@ -86,6 +92,7 @@
 
         val bounds = bubbleBarViewController.bubbleBarBounds
         val horizontalMargin = bubbleBarViewController.horizontalMargin
+        bubbleBarLocationListener.onBubbleBarLocationAnimated(location)
         dropTargetView?.updateLayoutParams<FrameLayout.LayoutParams> {
             width = bounds.width()
             height = bounds.height()
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
index 14f6e3a..d454fd7 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
@@ -95,11 +95,11 @@
 
     private static final long FADE_OUT_ANIM_ALPHA_DURATION_MS = 50L;
     private static final long FADE_OUT_ANIM_ALPHA_DELAY_MS = 50L;
-    private static final long FADE_OUT_ANIM_POSITION_DURATION_MS = 100L;
+    public static final long FADE_OUT_ANIM_POSITION_DURATION_MS = 100L;
     // During fade out animation we shift the bubble bar 1/80th of the screen width
     private static final float FADE_OUT_ANIM_POSITION_SHIFT = 1 / 80f;
 
-    private static final long FADE_IN_ANIM_ALPHA_DURATION_MS = 100L;
+    public static final long FADE_IN_ANIM_ALPHA_DURATION_MS = 100L;
     // Use STIFFNESS_MEDIUMLOW which is not defined in the API constants
     private static final float FADE_IN_ANIM_POSITION_SPRING_STIFFNESS = 400f;
     // During fade in animation we shift the bubble bar 1/60th of the screen width
@@ -187,6 +187,9 @@
     private BubbleView mDismissedByDragBubbleView;
     private float mAlphaDuringDrag = 1f;
 
+    /** Additional translation in the y direction that is applied to each bubble */
+    private float mBubbleOffsetY;
+
     private Controller mController;
 
     private int mPreviousLayoutDirection = LayoutDirection.UNDEFINED;
@@ -335,6 +338,16 @@
     }
 
     /**
+     * Sets offset of each bubble view in the y direction from the base position in the bar.
+     */
+    public void setBubbleOffsetY(float offsetY) {
+        mBubbleOffsetY = offsetY;
+        for (int i = 0; i < getChildCount(); i++) {
+            getChildAt(i).setTranslationY(getBubbleTranslationY());
+        }
+    }
+
+    /**
      * Sets new icon sizes and newBubbleBarPadding between icons and bubble bar borders.
      *
      * @param newIconSize         new icon size
@@ -676,7 +689,7 @@
         }
         setAlphaDuringBubbleDrag(1f);
         setTranslationX(0f);
-        if (getBubbleChildCount() > 0) {
+        if (mIsBarExpanded && getBubbleChildCount() > 0) {
             setAlpha(1f);
         }
     }
@@ -997,10 +1010,7 @@
         final float expandedWidth = expandedWidth();
         final float collapsedWidth = collapsedWidth();
         int childCount = getChildCount();
-        float viewBottom = mBubbleBarBounds.height() + (isExpanded() ? mPointerSize : 0);
-        float bubbleBarAnimatedTop = viewBottom - getBubbleBarHeight();
-        // When translating X & Y the scale is ignored, so need to deduct it from the translations
-        final float ty = bubbleBarAnimatedTop + mBubbleBarPadding - getScaleIconShift();
+        final float ty = getBubbleTranslationY();
         final boolean onLeft = bubbleBarLocation.isOnLeft(isLayoutRtl());
         // elevation state is opposite to widthState - when expanded all icons are flat
         float elevationState = (1 - widthState);
@@ -1125,15 +1135,25 @@
                 translationX = 0f;
             }
         } else {
-            if (bubbleIndex == 1 && getBubbleChildCount() >= MAX_VISIBLE_BUBBLES_COLLAPSED) {
-                translationX = mIconOverlapAmount;
-            } else {
+            // when the bar is on the right, the first bubble always has translation 0. the only
+            // case where another bubble has translation 0 is when we only have 1 bubble and the
+            // overflow. otherwise all other bubbles should be shifted by the overlap amount.
+            if (bubbleIndex == 0 || getBubbleChildCount() == 1) {
                 translationX = 0f;
+            } else {
+                translationX = mIconOverlapAmount;
             }
         }
         return mBubbleBarPadding + translationX - getScaleIconShift();
     }
 
+    private float getBubbleTranslationY() {
+        float viewBottom = mBubbleBarBounds.height() + (isExpanded() ? mPointerSize : 0);
+        float bubbleBarAnimatedTop = viewBottom - getBubbleBarHeight();
+        // When translating X & Y the scale is ignored, so need to deduct it from the translations
+        return mBubbleOffsetY + bubbleBarAnimatedTop + mBubbleBarPadding - getScaleIconShift();
+    }
+
     /**
      * Reorders the views to match the provided list.
      */
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
index 570b1b9..ed08de5 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
@@ -18,11 +18,8 @@
 import static android.view.View.INVISIBLE;
 import static android.view.View.VISIBLE;
 
-import static com.android.launcher3.taskbar.bubbles.BubbleView.STASH_TRANSLATION_Y;
-
 import android.animation.Animator;
 import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
 import android.content.res.Resources;
 import android.graphics.Point;
 import android.graphics.PointF;
@@ -97,6 +94,8 @@
             this::updateBackgroundScaleY);
     private final AnimatedFloat mBubbleBarTranslationY = new AnimatedFloat(
             this::updateTranslationY);
+    private final AnimatedFloat mBubbleOffsetY = new AnimatedFloat(
+            this::updateBubbleOffsetY);
 
     // Modified when swipe up is happening on the bubble bar or task bar.
     private float mBubbleBarSwipeUpTranslationY;
@@ -299,6 +298,10 @@
         return mBubbleBarTranslationY;
     }
 
+    public AnimatedFloat getBubbleOffsetY() {
+        return mBubbleOffsetY;
+    }
+
     public float getBubbleBarCollapsedWidth() {
         return mBarView.collapsedWidth();
     }
@@ -576,6 +579,10 @@
         mBarView.setBubbleAlpha(alpha);
     }
 
+    private void updateBubbleOffsetY(float transY) {
+        mBarView.setBubbleOffsetY(transY);
+    }
+
     private void updateBackgroundAlpha(float alpha) {
         mBarView.setBackgroundAlpha(alpha);
     }
@@ -874,14 +881,9 @@
         mBubbleStashController.getHandleBounds(stashedHandleBounds);
         int childCount = mBarView.getChildCount();
         float newChildWidth = (float) stashedHandleBounds.width() / childCount;
-        float stashTranslationY = -mBubbleStashController.getBubbleBarTranslationY();
         AnimatorSet animatorSet = new AnimatorSet();
         for (int i = 0; i < childCount; i++) {
             BubbleView child = (BubbleView) mBarView.getChildAt(i);
-            final float startTransY = isStashed ? 0f : stashTranslationY;
-            final float endTransY = isStashed ? stashTranslationY : 0f;
-            animatorSet.play(
-                    ObjectAnimator.ofFloat(child, STASH_TRANSLATION_Y, startTransY, endTransY));
             animatorSet.play(
                     createRevealAnimForBubble(child, isStashed, stashedHandleBounds,
                             newChildWidth));
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java
index e00916a..12df6f0 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java
@@ -76,7 +76,14 @@
      * in constructors for now, as some controllers may still be waiting for init().
      */
     public void init(TaskbarControllers taskbarControllers) {
+        // TODO(b/346381754) add TaskbarLauncherStateController implementation to adjust the hotseat
+        BubbleBarLocationCompositeListener bubbleBarLocationListeners =
+                new BubbleBarLocationCompositeListener(
+                        taskbarControllers.navbarButtonsViewController,
+                        taskbarControllers.taskbarViewController
+                );
         bubbleBarController.init(this,
+                bubbleBarLocationListeners,
                 taskbarControllers.navbarButtonsViewController::isImeVisible);
         bubbleStashedHandleViewController.ifPresent(
                 controller -> controller.init(/* bubbleControllers = */ this));
@@ -102,7 +109,7 @@
                 });
         bubbleDragController.init(/* bubbleControllers = */ this);
         bubbleDismissController.init(/* bubbleControllers = */ this);
-        bubbleBarPinController.init(this);
+        bubbleBarPinController.init(this, bubbleBarLocationListeners);
         bubblePinController.init(this);
 
         mPostInitRunnables.executeAllAndDestroy();
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
index cc6b49a..561df5c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
@@ -27,7 +27,6 @@
 import android.os.Bundle;
 import android.text.TextUtils;
 import android.util.AttributeSet;
-import android.util.FloatProperty;
 import android.view.LayoutInflater;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.ImageView;
@@ -50,27 +49,12 @@
 
     public static final int DEFAULT_PATH_SIZE = 100;
 
-    public static FloatProperty<BubbleView> STASH_TRANSLATION_Y = new FloatProperty<>(
-            "stashTranslationY") {
-        @Override
-        public void setValue(BubbleView bubbleView, float transY) {
-            bubbleView.setStashTranslationY(transY);
-        }
-
-        @Override
-        public Float get(BubbleView bubbleView) {
-            return bubbleView.mStashTranslationY;
-        }
-    };
-
     private final ImageView mBubbleIcon;
     private final ImageView mAppIcon;
     private int mBubbleSize;
 
     private float mDragTranslationX;
     private float mOffsetX;
-    private float mTranslationY;
-    private float mStashTranslationY;
 
     private DotRenderer mDotRenderer;
     private DotRenderer.DrawParams mDrawParams;
@@ -177,24 +161,6 @@
         setTranslationX(mDragTranslationX + mOffsetX);
     }
 
-    /**
-     * Set translation in y direction during stash and unstash from handle
-     */
-    public void setStashTranslationY(float translationY) {
-        mStashTranslationY = translationY;
-        applyTranslationY();
-    }
-
-    @Override
-    public void setTranslationY(float translationY) {
-        mTranslationY = translationY;
-        applyTranslationY();
-    }
-
-    private void applyTranslationY() {
-        super.setTranslationY(mTranslationY + mStashTranslationY);
-    }
-
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt
index 5d1e890..4f0337d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt
@@ -47,7 +47,7 @@
 
 class TransientBubbleStashController(
     private val taskbarHotseatDimensionsProvider: TaskbarHotseatDimensionsProvider,
-    private val context: Context
+    private val context: Context,
 ) : BubbleStashController {
 
     private lateinit var bubbleBarViewController: BubbleBarViewController
@@ -70,6 +70,7 @@
     private lateinit var bubbleBarBubbleAlpha: AnimatedFloat
     private lateinit var bubbleBarBackgroundAlpha: AnimatedFloat
     private lateinit var bubbleBarTranslationYAnimator: AnimatedFloat
+    private lateinit var bubbleBarBubbleTranslationY: AnimatedFloat
     private lateinit var bubbleBarBackgroundScaleX: AnimatedFloat
     private lateinit var bubbleBarBackgroundScaleY: AnimatedFloat
     private val handleCenterFromScreenBottom =
@@ -143,13 +144,14 @@
         taskbarInsetsController: TaskbarInsetsController,
         bubbleBarViewController: BubbleBarViewController,
         bubbleStashedHandleViewController: BubbleStashedHandleViewController?,
-        controllersAfterInitAction: ControllersAfterInitAction
+        controllersAfterInitAction: ControllersAfterInitAction,
     ) {
         this.taskbarInsetsController = taskbarInsetsController
         this.bubbleBarViewController = bubbleBarViewController
         this.bubbleStashedHandleViewController = bubbleStashedHandleViewController
         this.controllersAfterInitAction = controllersAfterInitAction
         bubbleBarTranslationYAnimator = bubbleBarViewController.bubbleBarTranslationY
+        bubbleBarBubbleTranslationY = bubbleBarViewController.bubbleOffsetY
         // bubble bar has only alpha property, getting it at index 0
         bubbleBarAlpha = bubbleBarViewController.bubbleBarAlpha.get(/* index= */ 0)
         bubbleBarBubbleAlpha = bubbleBarViewController.bubbleBarBubbleAlpha
@@ -170,7 +172,7 @@
                 bubbleBarTranslationYAnimator.animateToValue(bubbleBarTranslationY),
                 bubbleBarAlpha.animateToValue(1f),
                 bubbleBarBubbleAlpha.animateToValue(1f),
-                bubbleBarBackgroundAlpha.animateToValue(1f)
+                bubbleBarBackgroundAlpha.animateToValue(1f),
             )
         } else {
             isStashed = true
@@ -301,23 +303,24 @@
     private fun createStashAnimator(isStashed: Boolean, duration: Long): AnimatorSet {
         val animatorSet = AnimatorSet()
 
-        val alphaDuration = if (isStashed) duration else TASKBAR_STASH_ALPHA_DURATION
-        val alphaDelay = if (isStashed) TASKBAR_STASH_ALPHA_START_DELAY else 0L
         animatorSet.play(
             createBackgroundAlphaAnimator(isStashed).apply {
+                val alphaDuration = if (isStashed) duration else TASKBAR_STASH_ALPHA_DURATION
+                val alphaDelay = if (isStashed) TASKBAR_STASH_ALPHA_START_DELAY else 0L
                 this.duration = max(0L, alphaDuration - alphaDelay)
                 this.startDelay = alphaDelay
                 this.interpolator = LINEAR
             }
         )
 
-        val iconAlphaTarget = if (isStashed) 0f else 1f
         animatorSet.play(
-            bubbleBarBubbleAlpha.animateToValue(iconAlphaTarget).apply {
-                this.duration = TASKBAR_STASH_ALPHA_DURATION
-                this.startDelay = TASKBAR_STASH_ALPHA_START_DELAY
-                this.interpolator = LINEAR
-            }
+            bubbleBarBubbleAlpha
+                .animateToValue(getBarAlphaStart(isStashed), getBarAlphaEnd(isStashed))
+                .apply {
+                    this.duration = TASKBAR_STASH_ALPHA_DURATION
+                    this.startDelay = TASKBAR_STASH_ALPHA_START_DELAY
+                    this.interpolator = LINEAR
+                }
         )
 
         animatorSet.play(
@@ -334,6 +337,16 @@
             }
         )
 
+        // Animate bubble translation to keep reveal animation in the bounds of the bar
+        val bubbleTyStart = if (isStashed) 0f else -bubbleBarTranslationY
+        val bubbleTyEnd = if (isStashed) -bubbleBarTranslationY else 0f
+        animatorSet.play(
+            bubbleBarBubbleTranslationY.animateToValue(bubbleTyStart, bubbleTyEnd).apply {
+                this.duration = duration
+                this.interpolator = EMPHASIZED
+            }
+        )
+
         animatorSet.play(
             bubbleStashedHandleViewController?.createRevealAnimToIsStashed(isStashed)?.apply {
                 this.duration = duration
@@ -359,11 +372,15 @@
         )
 
         animatorSet.doOnStart {
-            if (!isStashed) {
-                bubbleBarBackgroundAlpha.updateValue(0f)
-                bubbleBarBubbleAlpha.updateValue(0f)
-                bubbleBarAlpha.value = 1f
-            }
+            // Update the start value for bubble view and background alpha when the entire animation
+            // begins.
+            // Alpha animation has a delay, and if we set the initial values at the start of the
+            // alpha animation, it will cause flickers.
+            bubbleBarBubbleAlpha.updateValue(getBarAlphaStart(isStashed))
+            bubbleBarBackgroundAlpha.updateValue(getBarAlphaStart(isStashed))
+            // We animate alpha for background and bubble views separately. Make sure the container
+            // is always visible.
+            bubbleBarAlpha.value = 1f
         }
         animatorSet.doOnEnd {
             animator = null
@@ -373,6 +390,9 @@
                     // reset bubble view alpha
                     bubbleBarBubbleAlpha.updateValue(1f)
                     bubbleBarBackgroundAlpha.updateValue(1f)
+                    // reset stash translation
+                    translationYDuringStash.updateValue(0f)
+                    bubbleBarBubbleTranslationY.updateValue(0f)
                     bubbleBarViewController.isExpanded = false
                 }
                 taskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged()
@@ -382,14 +402,29 @@
     }
 
     private fun createBackgroundAlphaAnimator(isStashed: Boolean): AnimatorSet {
-        val stashHandleAlphaTarget = if (isStashed) 1f else 0f
-        val barAlphaTarget = if (isStashed) 0f else 1f
         return AnimatorSet().apply {
-            play(bubbleBarBackgroundAlpha.animateToValue(barAlphaTarget))
-            play(stashHandleViewAlpha?.animateToValue(stashHandleAlphaTarget))
+            play(
+                bubbleBarBackgroundAlpha.animateToValue(
+                    getBarAlphaStart(isStashed),
+                    getBarAlphaEnd(isStashed),
+                )
+            )
+            play(stashHandleViewAlpha?.animateToValue(getHandleAlphaEnd(isStashed)))
         }
     }
 
+    private fun getBarAlphaStart(isStashed: Boolean): Float {
+        return if (isStashed) 1f else 0f
+    }
+
+    private fun getBarAlphaEnd(isStashed: Boolean): Float {
+        return if (isStashed) 0f else 1f
+    }
+
+    private fun getHandleAlphaEnd(isStashed: Boolean): Float {
+        return if (isStashed) 1f else 0f
+    }
+
     private fun createSpringOnStashAnimator(isStashed: Boolean): Animator {
         if (!isStashed) {
             // Animate the stash translation back to 0
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
index 1ba784b..18d717f 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
@@ -50,9 +50,11 @@
             return super.getVerticalProgress(launcher);
         }
         RecentsView recentsView = launcher.getOverviewPanel();
-        int transitionLength = LayoutUtils.getShelfTrackingDistance(launcher,
+        int transitionLength = LayoutUtils.getShelfTrackingDistance(
+                launcher,
                 launcher.getDeviceProfile(),
-                recentsView.getPagedOrientationHandler());
+                recentsView.getPagedOrientationHandler(),
+                recentsView.getSizeStrategy());
         AllAppsTransitionController controller = launcher.getAllAppsController();
         float scrollRange = Math.max(controller.getShiftRange(), 1);
         float progressDelta = (transitionLength / scrollRange);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index 0da7b2d..9164405 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -129,7 +129,10 @@
         mRecentsView = mLauncher.getOverviewPanel();
         mXRange = mLauncher.getDeviceProfile().widthPx / 2f;
         mYRange = LayoutUtils.getShelfTrackingDistance(
-            mLauncher, mLauncher.getDeviceProfile(), mRecentsView.getPagedOrientationHandler());
+                mLauncher,
+                mLauncher.getDeviceProfile(),
+                mRecentsView.getPagedOrientationHandler(),
+                mRecentsView.getSizeStrategy());
         mMaxYProgress = mLauncher.getDeviceProfile().heightPx / mYRange;
         mMotionPauseDetector = new MotionPauseDetector(mLauncher);
         mMotionPauseMinDisplacement = mLauncher.getResources().getDimension(
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
index b6a0ab3..b562838 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
@@ -142,8 +142,11 @@
                     .createPlaybackController();
             mLauncher.getStateManager().setCurrentUserControlledAnimation(mCurrentAnimation);
             RecentsView recentsView = mLauncher.getOverviewPanel();
-            totalShift = LayoutUtils.getShelfTrackingDistance(mLauncher,
-                    mLauncher.getDeviceProfile(), recentsView.getPagedOrientationHandler());
+            totalShift = LayoutUtils.getShelfTrackingDistance(
+                    mLauncher,
+                    mLauncher.getDeviceProfile(),
+                    recentsView.getPagedOrientationHandler(),
+                    recentsView.getSizeStrategy());
         } else {
             mCurrentAnimation = mLauncher.getStateManager()
                     .createAnimationToNewWorkspace(mToState, config);
diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index 5308436..85312e4 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -76,7 +76,7 @@
                 && DisplayController.getNavigationMode(context) != NavigationMode.NO_BUTTON) {
             return dp.isSeascape() ? outRect.left : (dp.widthPx - outRect.right);
         } else {
-            return LayoutUtils.getShelfTrackingDistance(context, dp, orientationHandler);
+            return LayoutUtils.getShelfTrackingDistance(context, dp, orientationHandler, this);
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java
index 9284e13..5ad55ae 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java
@@ -184,7 +184,7 @@
                             if (!mHasPassedTaskbarNavThreshold && passedTaskbarNavThreshold
                                     && !mGestureState.isInExtendedSlopRegion()) {
                                 mHasPassedTaskbarNavThreshold = true;
-                                mTaskbarActivityContext.onSwipeToUnstashTaskbar();
+                                mTaskbarActivityContext.onSwipeToUnstashTaskbar(true);
                             }
 
                             if (dY < 0) {
@@ -287,7 +287,7 @@
             // start a single unstash timeout if hovering bottom edge under the hinted taskbar.
             if (!sUnstashHandler.hasMessagesOrCallbacks()) {
                 sUnstashHandler.postDelayed(() -> {
-                    mTaskbarActivityContext.onSwipeToUnstashTaskbar();
+                    mTaskbarActivityContext.onSwipeToUnstashTaskbar(false);
                     mIsStashedTaskbarHovered = false;
                 }, HOVER_TASKBAR_UNSTASH_TIMEOUT);
             }
@@ -315,7 +315,7 @@
             startStashedTaskbarHover(/* isHovered = */ true);
         } else if (mBottomEdgeBounds.contains(x, y)) {
             // If hover screen's bottom edge not below the stashed taskbar, unstash it.
-            mTaskbarActivityContext.onSwipeToUnstashTaskbar();
+            mTaskbarActivityContext.onSwipeToUnstashTaskbar(false);
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt b/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt
index 1716f2e..5cf6823 100644
--- a/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt
+++ b/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt
@@ -24,7 +24,7 @@
 
 class RecentsViewModel(
     private val recentsTasksRepository: RecentTasksRepository,
-    private val recentsViewData: RecentsViewData
+    private val recentsViewData: RecentsViewData,
 ) {
     fun refreshAllTaskData() {
         recentsTasksRepository.getAllTaskData(true)
@@ -58,7 +58,8 @@
         recentsViewData.thumbnailSplashProgress.value = taskThumbnailSplashAlpha
     }
 
-    suspend fun waitForThumbnailsToUpdate(updatedThumbnails: Map<Int, ThumbnailData>) {
+    suspend fun waitForThumbnailsToUpdate(updatedThumbnails: Map<Int, ThumbnailData>?) {
+        if (updatedThumbnails.isNullOrEmpty()) return
         combine(
                 updatedThumbnails.map {
                     recentsTasksRepository.getThumbnailById(it.key).filter { thumbnailData ->
diff --git a/quickstep/src/com/android/quickstep/util/LayoutUtils.java b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
index b9338a3..a8460c9 100644
--- a/quickstep/src/com/android/quickstep/util/LayoutUtils.java
+++ b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
@@ -23,7 +23,7 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.NavigationMode;
-import com.android.quickstep.LauncherActivityInterface;
+import com.android.quickstep.BaseContainerInterface;
 import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
 
 public class LayoutUtils {
@@ -41,11 +41,14 @@
         return swipeHeight;
     }
 
-    public static int getShelfTrackingDistance(Context context, DeviceProfile dp,
-            RecentsPagedOrientationHandler orientationHandler) {
+    public static int getShelfTrackingDistance(
+            Context context,
+            DeviceProfile dp,
+            RecentsPagedOrientationHandler orientationHandler,
+            BaseContainerInterface<?, ?> baseContainerInterface) {
         // Track the bottom of the window.
         Rect taskSize = new Rect();
-        LauncherActivityInterface.INSTANCE.calculateTaskSize(context, dp, taskSize,
+        baseContainerInterface.calculateTaskSize(context, dp, taskSize,
                 orientationHandler);
         return orientationHandler.getDistanceToBottomOfRect(dp, taskSize);
     }
diff --git a/quickstep/src/com/android/quickstep/views/RecentsViewModelHelper.kt b/quickstep/src/com/android/quickstep/views/RecentsViewModelHelper.kt
index 4604b70..f22c672 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsViewModelHelper.kt
+++ b/quickstep/src/com/android/quickstep/views/RecentsViewModelHelper.kt
@@ -49,9 +49,7 @@
         recentsViewModel.setRunningTaskShowScreenshot(true)
         viewAttachedScope.launch {
             recentsViewModel.waitForRunningTaskShowScreenshotToUpdate()
-            if (updatedThumbnails != null) {
-                recentsViewModel.waitForThumbnailsToUpdate(updatedThumbnails)
-            }
+            recentsViewModel.waitForThumbnailsToUpdate(updatedThumbnails)
             ViewUtils.postFrameDrawn(taskView, onFinishRunnable)
         }
     }
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashControllerTest.kt
index 1d13956..d4a3b3a 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashControllerTest.kt
@@ -32,6 +32,7 @@
 import com.android.launcher3.taskbar.bubbles.BubbleBarView
 import com.android.launcher3.taskbar.bubbles.BubbleBarViewController
 import com.android.launcher3.taskbar.bubbles.BubbleStashedHandleViewController
+import com.android.launcher3.taskbar.bubbles.BubbleView
 import com.android.launcher3.util.MultiValueAlpha
 import com.android.wm.shell.shared.animation.PhysicsAnimator
 import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils
@@ -55,8 +56,8 @@
 
     companion object {
         const val TASKBAR_BOTTOM_SPACE = 5
-        const val BUBBLE_BAR_WIDTH = 200f
-        const val BUBBLE_BAR_HEIGHT = 100f
+        const val BUBBLE_BAR_WIDTH = 200
+        const val BUBBLE_BAR_HEIGHT = 100
         const val HOTSEAT_TRANSLATION_Y = -45f
         const val TASK_BAR_TRANSLATION_Y = -TASKBAR_BOTTOM_SPACE
         const val HANDLE_VIEW_WIDTH = 150
@@ -77,10 +78,12 @@
     private val context = ApplicationProvider.getApplicationContext<Context>()
     private lateinit var bubbleBarView: BubbleBarView
     private lateinit var stashedHandleView: StashedHandleView
+    private lateinit var bubbleView: BubbleView
     private lateinit var barTranslationY: AnimatedFloat
     private lateinit var barScaleX: AnimatedFloat
     private lateinit var barScaleY: AnimatedFloat
     private lateinit var barAlpha: MultiValueAlpha
+    private lateinit var bubbleOffsetY: AnimatedFloat
     private lateinit var bubbleAlpha: AnimatedFloat
     private lateinit var backgroundAlpha: AnimatedFloat
     private lateinit var stashedHandleAlpha: MultiValueAlpha
@@ -105,7 +108,7 @@
             taskbarInsetsController,
             bubbleBarViewController,
             bubbleStashedHandleViewController,
-            ImmediateAction()
+            ImmediateAction(),
         )
     }
 
@@ -161,11 +164,13 @@
         mTransientBubbleStashController.isStashed = false
         whenever(bubbleBarViewController.isHiddenForNoBubbles).thenReturn(false)
 
+        val bubbleInitialTranslation = bubbleView.translationY
+
         // When stash
         getInstrumentation().runOnMainSync {
             mTransientBubbleStashController.updateStashedAndExpandedState(
                 stash = true,
-                expand = false
+                expand = false,
             )
         }
 
@@ -181,9 +186,13 @@
         assertThat(bubbleBarView.alpha).isEqualTo(0f)
         assertThat(bubbleBarView.scaleX).isEqualTo(mTransientBubbleStashController.getStashScaleX())
         assertThat(bubbleBarView.scaleY).isEqualTo(mTransientBubbleStashController.getStashScaleY())
+        assertThat(bubbleBarView.background.alpha).isEqualTo(255)
         // Handle view is visible
         assertThat(stashedHandleView.translationY).isEqualTo(0)
         assertThat(stashedHandleView.alpha).isEqualTo(1)
+        // Bubble view is reset
+        assertThat(bubbleView.translationY).isEqualTo(bubbleInitialTranslation)
+        assertThat(bubbleView.alpha).isEqualTo(1f)
     }
 
     @Test
@@ -274,7 +283,7 @@
         val height = mTransientBubbleStashController.getTouchableHeight()
 
         // Then bubble bar height is returned
-        assertThat(height).isEqualTo(BUBBLE_BAR_HEIGHT.toInt())
+        assertThat(height).isEqualTo(BUBBLE_BAR_HEIGHT)
     }
 
     private fun advanceTimeBy(advanceMs: Long) {
@@ -285,20 +294,26 @@
     private fun setUpBubbleBarView() {
         getInstrumentation().runOnMainSync {
             bubbleBarView = BubbleBarView(context)
-            bubbleBarView.layoutParams = FrameLayout.LayoutParams(0, 0)
+            bubbleBarView.layoutParams =
+                FrameLayout.LayoutParams(BUBBLE_BAR_WIDTH, BUBBLE_BAR_HEIGHT)
+            bubbleView = BubbleView(context)
+            bubbleBarView.addBubble(bubbleView)
+            bubbleBarView.layout(0, 0, BUBBLE_BAR_WIDTH, BUBBLE_BAR_HEIGHT)
         }
     }
 
     private fun setUpStashedHandleView() {
         getInstrumentation().runOnMainSync {
             stashedHandleView = StashedHandleView(context)
-            stashedHandleView.layoutParams = FrameLayout.LayoutParams(0, 0)
+            stashedHandleView.layoutParams =
+                FrameLayout.LayoutParams(HANDLE_VIEW_WIDTH, HANDLE_VIEW_HEIGHT)
         }
     }
 
     private fun setUpBubbleBarController() {
         barTranslationY =
             AnimatedFloat(Runnable { bubbleBarView.translationY = barTranslationY.value })
+        bubbleOffsetY = AnimatedFloat { value -> bubbleBarView.setBubbleOffsetY(value) }
         barScaleX = AnimatedFloat { value -> bubbleBarView.scaleX = value }
         barScaleY = AnimatedFloat { value -> bubbleBarView.scaleY = value }
         barAlpha = MultiValueAlpha(bubbleBarView, 1 /* num alpha channels */)
@@ -307,13 +322,16 @@
 
         whenever(bubbleBarViewController.hasBubbles()).thenReturn(true)
         whenever(bubbleBarViewController.bubbleBarTranslationY).thenReturn(barTranslationY)
+        whenever(bubbleBarViewController.bubbleOffsetY).thenReturn(bubbleOffsetY)
         whenever(bubbleBarViewController.bubbleBarBackgroundScaleX).thenReturn(barScaleX)
         whenever(bubbleBarViewController.bubbleBarBackgroundScaleY).thenReturn(barScaleY)
         whenever(bubbleBarViewController.bubbleBarAlpha).thenReturn(barAlpha)
         whenever(bubbleBarViewController.bubbleBarBubbleAlpha).thenReturn(bubbleAlpha)
         whenever(bubbleBarViewController.bubbleBarBackgroundAlpha).thenReturn(backgroundAlpha)
-        whenever(bubbleBarViewController.bubbleBarCollapsedWidth).thenReturn(BUBBLE_BAR_WIDTH)
-        whenever(bubbleBarViewController.bubbleBarCollapsedHeight).thenReturn(BUBBLE_BAR_HEIGHT)
+        whenever(bubbleBarViewController.bubbleBarCollapsedWidth)
+            .thenReturn(BUBBLE_BAR_WIDTH.toFloat())
+        whenever(bubbleBarViewController.bubbleBarCollapsedHeight)
+            .thenReturn(BUBBLE_BAR_HEIGHT.toFloat())
         whenever(bubbleBarViewController.createRevealAnimatorForStashChange(any()))
             .thenReturn(AnimatorSet())
     }
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/viewmodel/RecentsViewModelTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/viewmodel/RecentsViewModelTest.kt
index fe67313..33d96a8 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/viewmodel/RecentsViewModelTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/viewmodel/RecentsViewModelTest.kt
@@ -70,6 +70,55 @@
         assertThat(thumbnailDataFlow2.first()).isNull()
     }
 
+    @Test
+    fun updatesRunningTaskShowScreenshot() = runTest {
+        systemUnderTest.setRunningTaskShowScreenshot(true)
+        systemUnderTest.waitForRunningTaskShowScreenshotToUpdate()
+    }
+
+    @Test
+    fun waitForThumbnailsToUpdate() = runTest {
+        // Given taskRepository with visible 2 tasks containing thumbnailData
+        val thumbnailData1 = createThumbnailData().apply { snapshotId = 1 }
+        val thumbnailData2 = createThumbnailData().apply { snapshotId = 2 }
+        tasksRepository.seedTasks(tasks)
+        tasksRepository.seedThumbnailData(mapOf(1 to thumbnailData1, 2 to thumbnailData2))
+        systemUnderTest.updateVisibleTasks(listOf(1, 2))
+
+        val thumbnailDataFlow1 = tasksRepository.getThumbnailById(1)
+        val thumbnailDataFlow2 = tasksRepository.getThumbnailById(2)
+
+        // Then getThumbnailById should initially contains correct thumbnailData
+        assertThat(thumbnailDataFlow1.first()).isEqualTo(thumbnailData1)
+        assertThat(thumbnailDataFlow2.first()).isEqualTo(thumbnailData2)
+
+        // When thumbnailData is updated in taskRepository
+        tasksRepository.seedThumbnailData(
+            mapOf(1 to thumbnailData1, 2 to createThumbnailData().apply { snapshotId = 3 })
+        )
+        // setVisibleTasks forces FakeTasksRepository to update the flows returned by
+        // getThumbnailById
+        tasksRepository.setVisibleTasks(listOf(1, 2))
+
+        // Then wait for thumbnailData should complete, and the previous getThumbnailById flow
+        // should return updated values
+        systemUnderTest.waitForThumbnailsToUpdate(
+            mapOf(2 to createThumbnailData().apply { snapshotId = 3 })
+        )
+        assertThat(thumbnailDataFlow1.first()).isEqualTo(thumbnailData1)
+        assertThat(thumbnailDataFlow2.first()?.snapshotId).isEqualTo(3)
+    }
+
+    @Test
+    fun waitForThumbnailsToUpdate_emptyMap() = runTest {
+        systemUnderTest.waitForThumbnailsToUpdate(emptyMap())
+    }
+
+    @Test
+    fun waitForThumbnailsToUpdate_null() = runTest {
+        systemUnderTest.waitForThumbnailsToUpdate(null)
+    }
+
     private fun createTaskWithId(taskId: Int) =
         Task(Task.TaskKey(taskId, 0, Intent(), ComponentName("", ""), 0, 2000)).apply {
             colorBackground = Color.argb(taskId, taskId, taskId, taskId)
diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
index 2e456a7..2858929 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -248,7 +248,6 @@
     }
 
     @Test
-    @ScreenRecordRule.ScreenRecord // b/355042336
     public void testOverview() throws IOException {
         startAppFast(getAppPackageName());
         startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 3e6436b..de2c506 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -350,7 +350,6 @@
 
     @Test
     @TaskbarModeSwitch
-    @ScreenRecord // b/358607191
     public void testQuickSwitchToPreviousAppForTablet() throws Exception {
         assumeTrue(mLauncher.isTablet());
         startTestActivity(2);
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index 2a6611f..e2be4ff 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -31,7 +31,7 @@
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Delt skjerm"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Appinformasjon for %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Bruksinnstillinger for %1$s"</string>
-    <string name="save_app_pair" msgid="5647523853662686243">"Lagre apptilkoblingen"</string>
+    <string name="save_app_pair" msgid="5647523853662686243">"Lagre app-paret"</string>
     <string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Denne apptilkoblingen støttes ikke på denne enheten"</string>
     <string name="app_pair_needs_unfold" msgid="4588897528143807002">"Åpne enheten for å bruke denne apptilkoblingen"</string>
diff --git a/src/com/android/launcher3/DropTargetHandler.kt b/src/com/android/launcher3/DropTargetHandler.kt
index e022159..f1029b1 100644
--- a/src/com/android/launcher3/DropTargetHandler.kt
+++ b/src/com/android/launcher3/DropTargetHandler.kt
@@ -35,8 +35,7 @@
                     target?.let {
                         deferred.mPackageName = it.packageName
                         mLauncher.addEventCallback(EVENT_RESUMED) { deferred.onLauncherResume() }
-                    }
-                        ?: deferred.sendFailure()
+                    } ?: deferred.sendFailure()
                 }
             }
         }
@@ -47,19 +46,10 @@
         mLauncher.appWidgetHolder.startConfigActivity(
             mLauncher,
             widgetId,
-            ActivityCodes.REQUEST_RECONFIGURE_APPWIDGET
+            ActivityCodes.REQUEST_RECONFIGURE_APPWIDGET,
         )
     }
 
-    fun dismissPrediction(
-        announcement: CharSequence,
-        onActionClicked: Runnable,
-        onDismiss: Runnable?
-    ) {
-        mLauncher.dragLayer.announceForAccessibility(announcement)
-        Snackbar.show(mLauncher, R.string.item_removed, R.string.undo, onDismiss, onActionClicked)
-    }
-
     fun getViewUnderDrag(info: ItemInfo): View? {
         return if (
             info is LauncherAppWidgetInfo &&
@@ -95,7 +85,7 @@
             R.string.item_removed,
             R.string.undo,
             mLauncher.modelWriter::commitDelete,
-            onUndoClicked
+            onUndoClicked,
         )
     }
 
diff --git a/src/com/android/launcher3/SecondaryDropTarget.java b/src/com/android/launcher3/SecondaryDropTarget.java
index 0a4fb73..8d1e61f 100644
--- a/src/com/android/launcher3/SecondaryDropTarget.java
+++ b/src/com/android/launcher3/SecondaryDropTarget.java
@@ -7,7 +7,6 @@
 import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.INVALID;
 import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.RECONFIGURE;
 import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.UNINSTALL;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_DISMISS_PREDICTION_UNDO;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROPPED_ON_DONT_SUGGEST;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROPPED_ON_UNINSTALL;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_UNINSTALL_CANCELLED;
@@ -36,7 +35,6 @@
 
 import androidx.annotation.Nullable;
 
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.logging.InstanceId;
@@ -242,8 +240,7 @@
 
     @Override
     public void completeDrop(final DragObject d) {
-        ComponentName target = performDropAction(getViewUnderDrag(d.dragInfo), d.dragInfo,
-                d.logInstanceId);
+        ComponentName target = performDropAction(getViewUnderDrag(d.dragInfo), d.dragInfo);
         mDropTargetHandler.onSecondaryTargetCompleteDrop(target, d);
     }
 
@@ -275,7 +272,7 @@
      * Performs the drop action and returns the target component for the dragObject or null if
      * the action was not performed.
      */
-    protected ComponentName performDropAction(View view, ItemInfo info, InstanceId instanceId) {
+    protected ComponentName performDropAction(View view, ItemInfo info) {
         if (mCurrentAccessibilityAction == RECONFIGURE) {
             int widgetId = getReconfigurableWidgetId(view);
             if (widgetId != INVALID_APPWIDGET_ID) {
@@ -283,21 +280,6 @@
             }
             return null;
         }
-        if (mCurrentAccessibilityAction == DISMISS_PREDICTION) {
-            if (FeatureFlags.ENABLE_DISMISS_PREDICTION_UNDO.get()) {
-                CharSequence announcement = getContext().getString(R.string.item_removed);
-                mDropTargetHandler
-                        .dismissPrediction(announcement, () -> {
-                        }, () -> {
-                            mStatsLogManager.logger()
-                                    .withInstanceId(instanceId)
-                                    .withItemInfo(info)
-                                    .log(LAUNCHER_DISMISS_PREDICTION_UNDO);
-                        });
-            }
-            return null;
-        }
-
         return performUninstall(getContext(), getUninstallTarget(getContext(), info), info);
     }
 
@@ -332,9 +314,8 @@
 
     @Override
     public void onAccessibilityDrop(View view, ItemInfo item) {
-        InstanceId instanceId = new InstanceIdSequence().newInstanceId();
-        doLog(instanceId, item);
-        performDropAction(view, item, instanceId);
+        doLog(new InstanceIdSequence().newInstanceId(), item);
+        performDropAction(view, item);
     }
 
     /**
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 66d9289..8fe1b34 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -63,10 +63,6 @@
      * <p>
      */
     // TODO(Block 3): Clean up flags
-    public static final BooleanFlag ENABLE_DISMISS_PREDICTION_UNDO = getDebugFlag(270394476,
-            "ENABLE_DISMISS_PREDICTION_UNDO", DISABLED,
-            "Show an 'Undo' snackbar when users dismiss a predicted hotseat item");
-
     public static final BooleanFlag ENABLE_WORKSPACE_LOADING_OPTIMIZATION = getDebugFlag(251502424,
             "ENABLE_WORKSPACE_LOADING_OPTIMIZATION", DISABLED,
             "load the current workspace screen visible to the user before the rest rather than "
@@ -226,10 +222,15 @@
     }
 
     // TODO(Block 29): Clean up flags
+    // Aconfig migration complete for ENABLE_ALL_APPS_BUTTON_IN_HOTSEAT.
     public static final BooleanFlag ENABLE_ALL_APPS_BUTTON_IN_HOTSEAT = getDebugFlag(270393897,
             "ENABLE_ALL_APPS_BUTTON_IN_HOTSEAT", DISABLED,
             "Enables displaying the all apps button in the hotseat.");
 
+    public static boolean enableAllAppsButtonInHotseat() {
+        return ENABLE_ALL_APPS_BUTTON_IN_HOTSEAT.get() || Flags.enableAllAppsButtonInHotseat();
+    }
+
     // TODO(Block 30): Clean up flags
     public static final BooleanFlag USE_SEARCH_REQUEST_TIMEOUT_OVERRIDES = getDebugFlag(270395010,
             "USE_SEARCH_REQUEST_TIMEOUT_OVERRIDES", DISABLED,
diff --git a/src/com/android/launcher3/model/AllAppsList.java b/src/com/android/launcher3/model/AllAppsList.java
index 64ebbf3..1f60f13 100644
--- a/src/com/android/launcher3/model/AllAppsList.java
+++ b/src/com/android/launcher3/model/AllAppsList.java
@@ -223,7 +223,8 @@
                     if (DEBUG) {
                         Log.w(TAG, "updatePromiseInstallInfo: removing app due to install"
                                 + " failure and appInfo not startable."
-                                + " package=" + appInfo.getTargetPackage());
+                                + " package=" + appInfo.getTargetPackage()
+                                + ", user=" + user);
                     }
                     removeApp(i);
                 }
@@ -319,7 +320,8 @@
                     if (!findActivity(matches, applicationInfo.componentName)) {
                         if (DEBUG) {
                             Log.w(TAG, "Changing shortcut target due to app component name change."
-                                    + " package=" + packageName);
+                                    + " component=" + applicationInfo.componentName
+                                    + ", user=" + user);
                         }
                         removeApp(i);
                     }
@@ -346,8 +348,9 @@
         } else {
             // Remove all data for this package.
             if (DEBUG) {
-                Log.w(TAG, "updatePromiseInstallInfo: no Activities matched updated package,"
-                        + " removing all apps from package=" + packageName);
+                Log.w(TAG, "updatePackage: no Activities matched updated package,"
+                        + " removing any AppInfo with package=" + packageName
+                        + ", user=" + user);
             }
             for (int i = data.size() - 1; i >= 0; i--) {
                 final AppInfo applicationInfo = data.get(i);
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index 2febb22..5464afe 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -119,7 +119,8 @@
         final HashMap<String, List<LauncherActivityInfo>> activitiesLists = new HashMap<>();
         if (DEBUG) {
             Log.d(TAG, "Package updated: mOp=" + getOpString()
-                    + " packages=" + Arrays.toString(packages));
+                    + " packages=" + Arrays.toString(packages)
+                    + ", user=" + mUser);
         }
         switch (mOp) {
             case OP_ADD: {
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index 1b245ab..63c9d94 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -1,5 +1,6 @@
 package com.android.launcher3.popup;
 
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_DISMISS_PREDICTION_UNDO;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_PRIVATE_SPACE_INSTALL_SYSTEM_SHORTCUT_TAP;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_PRIVATE_SPACE_UNINSTALL_SYSTEM_SHORTCUT_TAP;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_APP_INFO_TAP;
@@ -41,6 +42,7 @@
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.views.ActivityContext;
+import com.android.launcher3.views.Snackbar;
 import com.android.launcher3.widget.WidgetsBottomSheet;
 import com.android.launcher3.widget.picker.model.data.WidgetPickerData;
 
@@ -336,6 +338,14 @@
             mTarget.getStatsLogManager().logger()
                     .withItemInfo(mItemInfo)
                     .log(LAUNCHER_SYSTEM_SHORTCUT_DONT_SUGGEST_APP_TAP);
+            if (Flags.enableDismissPredictionUndo()) {
+                Snackbar.show(mTarget,
+                        view.getContext().getString(R.string.item_removed), R.string.undo,
+                        () -> { }, () ->
+                            mTarget.getStatsLogManager().logger()
+                                    .withItemInfo(mItemInfo)
+                                    .log(LAUNCHER_DISMISS_PREDICTION_UNDO));
+            }
         }
     }
 
diff --git a/src/com/android/launcher3/util/MultiTranslateDelegate.java b/src/com/android/launcher3/util/MultiTranslateDelegate.java
index 84ef445..38c87c8 100644
--- a/src/com/android/launcher3/util/MultiTranslateDelegate.java
+++ b/src/com/android/launcher3/util/MultiTranslateDelegate.java
@@ -37,6 +37,7 @@
     public static final int INDEX_TASKBAR_ALIGNMENT_ANIM = 3;
     public static final int INDEX_TASKBAR_REVEAL_ANIM = 4;
     public static final int INDEX_TASKBAR_PINNING_ANIM = 5;
+    public static final int INDEX_NAV_BAR_ANIM = 6;
 
     // Affect all items inside of a MultipageCellLayout
     public static final int INDEX_CELLAYOUT_MULTIPAGE_SPACING = 3;
@@ -47,7 +48,7 @@
     // Specific for hotseat items when adjusting for bubbles
     public static final int INDEX_BUBBLE_ADJUSTMENT_ANIM = 3;
 
-    public static final int COUNT = 6;
+    public static final int COUNT = 7;
 
     private final MultiPropertyFactory<View> mTranslationX;
     private final MultiPropertyFactory<View> mTranslationY;
diff --git a/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestProtocol.java b/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestProtocol.java
index ea58136..d7dd40b 100644
--- a/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestProtocol.java
+++ b/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestProtocol.java
@@ -170,7 +170,7 @@
     public static final String UIOBJECT_STALE_ELEMENT = "b/319501259";
     public static final String TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE = "b/326908466";
     public static final String WIDGET_CONFIG_NULL_EXTRA_INTENT = "b/324419890";
-    public static final String OVERVIEW_SELECT_TOOLTIP_MISALIGNED = "b/332485341";
+
     public static final String REQUEST_FLAG_ENABLE_GRID_ONLY_OVERVIEW = "enable-grid-only-overview";
     public static final String REQUEST_FLAG_ENABLE_APP_PAIRS = "enable-app-pairs";
 
diff --git a/tests/src/com/android/launcher3/dragging/TaplDragTest.java b/tests/src/com/android/launcher3/dragging/TaplDragTest.java
index 76c1948..8fe77ac 100644
--- a/tests/src/com/android/launcher3/dragging/TaplDragTest.java
+++ b/tests/src/com/android/launcher3/dragging/TaplDragTest.java
@@ -197,7 +197,6 @@
     @PlatinumTest(focusArea = "launcher")
     @Test
     @PortraitLandscape
-    @ScreenRecordRule.ScreenRecord // b/343953783
     public void testDragAppIcon() {
 
         final HomeAllApps allApps = mLauncher.getWorkspace().switchToAllApps();
diff --git a/tests/src/com/android/launcher3/popup/SystemShortcutTest.java b/tests/src/com/android/launcher3/popup/SystemShortcutTest.java
index dcfcad5..f54668c 100644
--- a/tests/src/com/android/launcher3/popup/SystemShortcutTest.java
+++ b/tests/src/com/android/launcher3/popup/SystemShortcutTest.java
@@ -19,19 +19,26 @@
 import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.launcher3.AbstractFloatingView.TYPE_SNACKBAR;
+import static com.android.launcher3.Flags.FLAG_ENABLE_DISMISS_PREDICTION_UNDO;
 import static com.android.launcher3.Flags.FLAG_ENABLE_PRIVATE_SPACE;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_ALL_APPS;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_DISMISS_PREDICTION_UNDO;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_DONT_SUGGEST_APP_TAP;
 import static com.android.launcher3.model.data.WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -50,8 +57,13 @@
 
 import androidx.test.annotation.UiThreadTest;
 import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.R;
 import com.android.launcher3.allapps.PrivateProfileManager;
+import com.android.launcher3.logging.StatsLogManager;
+import com.android.launcher3.logging.StatsLogManager.StatsLogger;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -61,8 +73,9 @@
 import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext;
 import com.android.launcher3.util.LauncherMultivalentJUnit;
 import com.android.launcher3.util.TestSandboxModelContextWrapper;
+import com.android.launcher3.util.TestUtil;
 import com.android.launcher3.util.UserIconInfo;
-import com.android.launcher3.views.BaseDragLayer;
+import com.android.launcher3.views.Snackbar;
 import com.android.launcher3.widget.picker.model.WidgetPickerDataProvider;
 import com.android.launcher3.widget.picker.model.data.WidgetPickerData;
 
@@ -72,6 +85,7 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Answers;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -88,25 +102,31 @@
     private PrivateProfileManager mPrivateProfileManager;
     private WidgetPickerDataProvider mWidgetPickerDataProvider;
     private AppInfo mAppInfo;
+
     @Mock UserCache mUserCache;
     @Mock ApiWrapper mApiWrapper;
-    @Mock BaseDragLayer mBaseDragLayer;
     @Mock UserIconInfo mUserIconInfo;
     @Mock LauncherActivityInfo mLauncherActivityInfo;
     @Mock ApplicationInfo mApplicationInfo;
     @Mock Intent mIntent;
+    @Mock StatsLogManager mStatsLogManager;
+    @Mock(answer = Answers.RETURNS_SELF) StatsLogger mStatsLogger;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mSandboxContext.putObject(UserCache.INSTANCE, mUserCache);
         mSandboxContext.putObject(ApiWrapper.INSTANCE, mApiWrapper);
-        mTestContext = new TestSandboxModelContextWrapper(mSandboxContext);
-        mView = new View(mSandboxContext);
-        spyOn(mTestContext);
+        mTestContext = new TestSandboxModelContextWrapper(mSandboxContext) {
+            @Override
+            public StatsLogManager getStatsLogManager() {
+                return mStatsLogManager;
+            }
+        };
         spyOn(mSandboxContext);
-        doReturn(mBaseDragLayer).when(mTestContext).getDragLayer();
+        doReturn(mStatsLogger).when(mStatsLogManager).logger();
 
+        mView = new View(mTestContext);
         mItemInfo = new ItemInfo();
 
         LauncherApps mLauncherApps = mSandboxContext.spyService(LauncherApps.class);
@@ -114,7 +134,6 @@
         when(mLauncherActivityInfo.getApplicationInfo()).thenReturn(mApplicationInfo);
 
         when(mUserCache.getUserInfo(any())).thenReturn(mUserIconInfo);
-        when(mBaseDragLayer.getChildCount()).thenReturn(0);
         mPrivateProfileManager = mTestContext.getAppsView().getPrivateProfileManager();
         spyOn(mPrivateProfileManager);
         when(mPrivateProfileManager.getProfileUser()).thenReturn(PRIVATE_HANDLE);
@@ -168,6 +187,7 @@
     }
 
     @Test
+    @DisableFlags(FLAG_ENABLE_DISMISS_PREDICTION_UNDO)
     public void testDontSuggestAppForPredictedItem() {
         mAppInfo = new AppInfo();
         mAppInfo.componentName = new ComponentName(mTestContext, getClass());
@@ -176,7 +196,36 @@
         SystemShortcut systemShortcut = SystemShortcut.DONT_SUGGEST_APP
                 .getShortcut(mTestContext, mAppInfo, mView);
         assertNotNull(systemShortcut);
-        systemShortcut.onClick(mView);
+
+        TestUtil.runOnExecutorSync(MAIN_EXECUTOR, () -> systemShortcut.onClick(mView));
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+        verify(mStatsLogger).log(eq(LAUNCHER_SYSTEM_SHORTCUT_DONT_SUGGEST_APP_TAP));
+        assertFalse(AbstractFloatingView.hasOpenView(mTestContext, TYPE_SNACKBAR));
+    }
+
+    @Test
+    @EnableFlags(FLAG_ENABLE_DISMISS_PREDICTION_UNDO)
+    public void testDontSuggestAppForPredictedItemWithUndo() {
+        mAppInfo = new AppInfo();
+        mAppInfo.componentName = new ComponentName(mTestContext, getClass());
+        mAppInfo.container = CONTAINER_HOTSEAT_PREDICTION;
+        assertTrue(mAppInfo.isPredictedItem());
+        SystemShortcut systemShortcut = SystemShortcut.DONT_SUGGEST_APP
+                .getShortcut(mTestContext, mAppInfo, mView);
+        assertNotNull(systemShortcut);
+
+        TestUtil.runOnExecutorSync(MAIN_EXECUTOR, () -> systemShortcut.onClick(mView));
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        verify(mStatsLogger).log(eq(LAUNCHER_SYSTEM_SHORTCUT_DONT_SUGGEST_APP_TAP));
+
+        // Undo bar shown
+        Snackbar snackbar = AbstractFloatingView.getOpenView(mTestContext, TYPE_SNACKBAR);
+        assertNotNull(snackbar);
+        reset(mStatsLogger);
+        TestUtil.runOnExecutorSync(MAIN_EXECUTOR, snackbar.findViewById(
+                R.id.action)::performClick);
+        verify(mStatsLogger).log(eq(LAUNCHER_DISMISS_PREDICTION_UNDO));
     }
 
     @Test
diff --git a/tests/src/com/android/launcher3/tablet/TaplIsTabletTest.kt b/tests/src/com/android/launcher3/tablet/TaplIsTabletTest.kt
new file mode 100644
index 0000000..a6de607
--- /dev/null
+++ b/tests/src/com/android/launcher3/tablet/TaplIsTabletTest.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2024 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.tablet
+
+import android.platform.test.rule.AllowedDevices
+import android.platform.test.rule.DeviceProduct
+import com.android.launcher3.Launcher
+import com.android.launcher3.ui.AbstractLauncherUiTest
+import junit.framework.TestCase.assertFalse
+import junit.framework.TestCase.assertTrue
+import org.junit.Test
+
+class TaplIsTabletTest : AbstractLauncherUiTest<Launcher>() {
+
+    /** Investigating b/366237798 by isolating and seeing flake rate of mLauncher.isTablet */
+    @Test
+    @AllowedDevices(
+        DeviceProduct.CF_FOLDABLE,
+        DeviceProduct.CF_TABLET,
+        DeviceProduct.TANGORPRO,
+        DeviceProduct.FELIX,
+        DeviceProduct.COMET,
+    )
+    fun isTabletShouldBeTrue() {
+        assertTrue(mLauncher.isTablet)
+    }
+
+    /** Investigating b/366237798 by isolating and seeing flake rate of mLauncher.isTablet */
+    @Test
+    @AllowedDevices(DeviceProduct.CF_PHONE, DeviceProduct.CHEETAH)
+    fun isTabletShouldBeFalse() {
+        assertFalse(mLauncher.isTablet)
+    }
+}
diff --git a/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java b/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java
index 20c5a25..2edd129 100644
--- a/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java
+++ b/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java
@@ -164,7 +164,6 @@
 
     @Test
     @PortraitLandscape
-    @ScreenRecordRule.ScreenRecord // b/352130094
     public void testDragIconToPage2() {
         Workspace workspace = mLauncher.getWorkspace();
 
@@ -242,7 +241,6 @@
         });
     }
 
-    @ScreenRecordRule.ScreenRecord // b/329935119
     @Test
     @PortraitLandscape
     public void testEmptyPageDoesNotGetRemovedIfPagePairIsNotEmpty() {