Merge "Migrating prediction_undo to aconfig" 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 eb3280e2..c0cdb9a 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"
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/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/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 0efa949..a1cd7f7 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;
@@ -1550,10 +1551,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();
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/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
index 14f6e3a..c005640 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
@@ -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
@@ -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/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/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/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/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..638ae7c 100644
--- a/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java
+++ b/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java
@@ -242,7 +242,6 @@
         });
     }
 
-    @ScreenRecordRule.ScreenRecord // b/329935119
     @Test
     @PortraitLandscape
     public void testEmptyPageDoesNotGetRemovedIfPagePairIsNotEmpty() {