Merge "Initial screenshot tests for bubble bar view" into main
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index 0da2df1..3769124 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -388,3 +388,13 @@
     description: "Refactor grid migration such that the code is simpler to understand and update"
     bug: "358399271"
 }
+
+flag {
+    name: "accessibility_scroll_on_allapps"
+    namespace: "launcher"
+    description: "Scroll to item position if accessibility focused"
+    bug: "265392261"
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/quickstep/res/values-ar/strings.xml b/quickstep/res/values-ar/strings.xml
index 29d214d..e691663 100644
--- a/quickstep/res/values-ar/strings.xml
+++ b/quickstep/res/values-ar/strings.xml
@@ -89,7 +89,7 @@
     <string name="gesture_tutorial_nice" msgid="2936275692616928280">"أحسنت"</string>
     <string name="gesture_tutorial_step" msgid="1279786122817620968">"الدليل التوجيهي <xliff:g id="CURRENT">%1$d</xliff:g> من إجمالي <xliff:g id="TOTAL">%2$d</xliff:g>"</string>
     <string name="allset_title" msgid="5021126669778966707">"اكتملت عملية الإعداد"</string>
-    <string name="allset_hint" msgid="459504134589971527">"يمكنك التمرير سريعًا إلى الأعلى للانتقال إلى الشاشة الرئيسية"</string>
+    <string name="allset_hint" msgid="459504134589971527">"مرّر سريعًا للأعلى للانتقال إلى الشاشة الرئيسية"</string>
     <string name="allset_button_hint" msgid="2395219947744706291">"انقر على زر الشاشة الرئيسية للانتقال إلى الشاشة الرئيسية."</string>
     <string name="allset_description_generic" msgid="5385500062202019855">"يمكنك الآن بدء استخدام <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="default_device_name" msgid="6660656727127422487">"الجهاز"</string>
diff --git a/quickstep/res/values-ky/strings.xml b/quickstep/res/values-ky/strings.xml
index 98f46ac..04e7f6e 100644
--- a/quickstep/res/values-ky/strings.xml
+++ b/quickstep/res/values-ky/strings.xml
@@ -92,7 +92,7 @@
     <string name="allset_hint" msgid="459504134589971527">"Башкы бетке өтүү үчүн экранды өйдө сүрүңүз"</string>
     <string name="allset_button_hint" msgid="2395219947744706291">"Башкы экранга өтүү үчүн башкы бет баскычын таптап коюңуз"</string>
     <string name="allset_description_generic" msgid="5385500062202019855">"<xliff:g id="DEVICE">%1$s</xliff:g> колдоно берсеңиз болот"</string>
-    <string name="default_device_name" msgid="6660656727127422487">"түзмөк"</string>
+    <string name="default_device_name" msgid="6660656727127422487">"Түзмөктү"</string>
     <string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Өтүү аракетинин системалык параметрлери"</annotation></string>
     <string name="action_share" msgid="2648470652637092375">"Бөлүшүү"</string>
     <string name="action_screenshot" msgid="8171125848358142917">"Скриншот"</string>
diff --git a/quickstep/res/values-ru/strings.xml b/quickstep/res/values-ru/strings.xml
index 4054eb7..2722ca9 100644
--- a/quickstep/res/values-ru/strings.xml
+++ b/quickstep/res/values-ru/strings.xml
@@ -93,7 +93,7 @@
     <string name="allset_button_hint" msgid="2395219947744706291">"Нажмите кнопку главного экрана, чтобы открыть его."</string>
     <string name="allset_description_generic" msgid="5385500062202019855">"Теперь вы можете использовать <xliff:g id="DEVICE">%1$s</xliff:g>."</string>
     <string name="default_device_name" msgid="6660656727127422487">"устройство"</string>
-    <string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Системные настройки навигации"</annotation></string>
+    <string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Настройки навигации в системе"</annotation></string>
     <string name="action_share" msgid="2648470652637092375">"Поделиться"</string>
     <string name="action_screenshot" msgid="8171125848358142917">"Скриншот"</string>
     <string name="action_split" msgid="2098009717623550676">"Разделить"</string>
diff --git a/quickstep/src/com/android/launcher3/QuickstepAccessibilityDelegate.java b/quickstep/src/com/android/launcher3/QuickstepAccessibilityDelegate.java
index 962fd91..1161720 100644
--- a/quickstep/src/com/android/launcher3/QuickstepAccessibilityDelegate.java
+++ b/quickstep/src/com/android/launcher3/QuickstepAccessibilityDelegate.java
@@ -15,10 +15,19 @@
  */
 package com.android.launcher3;
 
+import static androidx.recyclerview.widget.RecyclerView.NO_POSITION;
+
 import android.view.KeyEvent;
 import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.LinearSmoothScroller;
+import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
+import com.android.launcher3.allapps.AllAppsRecyclerView;
+import com.android.launcher3.allapps.SearchRecyclerView;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.uioverrides.PredictedAppIcon;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
@@ -26,14 +35,61 @@
 import java.util.List;
 
 public class QuickstepAccessibilityDelegate extends LauncherAccessibilityDelegate {
+    private QuickstepLauncher mLauncher;
 
     public QuickstepAccessibilityDelegate(QuickstepLauncher launcher) {
         super(launcher);
+        mLauncher = launcher;
         mActions.put(PIN_PREDICTION, new LauncherAction(
                 PIN_PREDICTION, R.string.pin_prediction, KeyEvent.KEYCODE_P));
     }
 
     @Override
+    public void onPopulateAccessibilityEvent(View view, AccessibilityEvent event) {
+        super.onPopulateAccessibilityEvent(view, event);
+        // Scroll to the position if focused view in main allapps list and not completely visible.
+        scrollToPositionIfNeeded(view);
+    }
+
+    private void scrollToPositionIfNeeded(View view) {
+        if (!Flags.accessibilityScrollOnAllapps()) {
+            return;
+        }
+        AllAppsRecyclerView contentView = mLauncher.getAppsView().getActiveRecyclerView();
+        if (contentView instanceof SearchRecyclerView) {
+            return;
+        }
+        LinearLayoutManager layoutManager = (LinearLayoutManager) contentView.getLayoutManager();
+        if (layoutManager == null) {
+            return;
+        }
+        RecyclerView.ViewHolder vh = contentView.findContainingViewHolder(view);
+        if (vh == null) {
+            return;
+        }
+        int itemPosition = vh.getBindingAdapterPosition();
+        if (itemPosition == NO_POSITION) {
+            return;
+        }
+        int firstCompletelyVisible = layoutManager.findFirstCompletelyVisibleItemPosition();
+        int lastCompletelyVisible = layoutManager.findLastCompletelyVisibleItemPosition();
+        boolean itemCompletelyVisible = firstCompletelyVisible <= itemPosition
+                && lastCompletelyVisible >= itemPosition;
+        if (itemCompletelyVisible) {
+            return;
+        }
+        RecyclerView.SmoothScroller smoothScroller =
+                new LinearSmoothScroller(mLauncher.asContext()) {
+                    @Override
+                    protected int getVerticalSnapPreference() {
+                        return LinearSmoothScroller.SNAP_TO_ANY;
+                    }
+                };
+        smoothScroller.setTargetPosition(itemPosition);
+        layoutManager.startSmoothScroll(smoothScroller);
+    }
+
+    @Override
     protected void getSupportedActions(View host, ItemInfo item, List<LauncherAction> out) {
         if (host instanceof PredictedAppIcon && !((PredictedAppIcon) host).isPinned()) {
             out.add(new LauncherAction(PIN_PREDICTION, R.string.pin_prediction,
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index e51c956..2457cfd 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -46,17 +46,13 @@
 import static com.android.launcher3.Flags.enableContainerReturnAnimations;
 import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
-import static com.android.launcher3.LauncherAnimUtils.VIEW_BACKGROUND_COLOR;
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.Utilities.mapBoundToRange;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_BACK_SWIPE_HOME_ANIMATION;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_SCRIM_FOR_APP_LAUNCH;
-import static com.android.launcher3.config.FeatureFlags.KEYGUARD_ANIMATION;
 import static com.android.launcher3.config.FeatureFlags.SEPARATE_RECENTS_ACTIVITY;
-import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
 import static com.android.launcher3.testing.shared.TestProtocol.WALLPAPER_OPEN_ANIMATION_FINISHED_MESSAGE;
 import static com.android.launcher3.util.DisplayController.isTransientTaskbar;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
@@ -89,7 +85,6 @@
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.RectF;
-import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
 import android.os.IBinder;
@@ -138,11 +133,9 @@
 import com.android.launcher3.uioverrides.QuickstepLauncher;
 import com.android.launcher3.util.ActivityOptionsWrapper;
 import com.android.launcher3.util.DynamicResource;
-import com.android.launcher3.util.ObjectWrapper;
 import com.android.launcher3.util.RunnableList;
-import com.android.launcher3.util.Themes;
+import com.android.launcher3.util.StableViewInfo;
 import com.android.launcher3.views.FloatingIconView;
-import com.android.launcher3.views.ScrimView;
 import com.android.launcher3.widget.LauncherAppWidgetHostView;
 import com.android.quickstep.LauncherBackAnimationController;
 import com.android.quickstep.RemoteAnimationTargets;
@@ -174,6 +167,7 @@
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.List;
 
@@ -258,7 +252,6 @@
     // Strong refs to runners which are cleared when the launcher activity is destroyed
     private RemoteAnimationFactory mWallpaperOpenRunner;
     private RemoteAnimationFactory mAppLaunchRunner;
-    private RemoteAnimationFactory mKeyguardGoingAwayRunner;
 
     private RemoteAnimationFactory mWallpaperOpenTransitionRunner;
     private RemoteTransition mLauncherOpenTransition;
@@ -327,7 +320,7 @@
      * @return ActivityOptions with remote animations that controls how the window of the opening
      * targets are displayed.
      */
-    public ActivityOptionsWrapper getActivityLaunchOptions(View v) {
+    public ActivityOptionsWrapper getActivityLaunchOptions(View v, ItemInfo itemInfo) {
         boolean fromRecents = isLaunchingFromRecents(v, null /* targets */);
         RunnableList onEndCallback = new RunnableList();
 
@@ -357,7 +350,7 @@
 
         IBinder cookie = mAppLaunchRunner.supportsReturnTransition()
                 ? ((ContainerAnimationRunner) mAppLaunchRunner).getCookie() : null;
-        addLaunchCookie(cookie, (ItemInfo) v.getTag(), options);
+        addLaunchCookie(cookie, itemInfo, options);
 
         // Register the return animation so it can be triggered on back from the app to home.
         maybeRegisterAppReturnTransition(v);
@@ -434,7 +427,7 @@
      */
     private void addLaunchCookie(IBinder cookie, ItemInfo info, ActivityOptions options) {
         if (cookie == null) {
-            cookie = mLauncher.getLaunchCookie(info);
+            cookie = StableViewInfo.toLaunchCookie(info);
         }
 
         if (cookie != null) {
@@ -667,34 +660,11 @@
                 launcherAnimator.play(scaleAnim);
             });
 
-            final boolean scrimEnabled = ENABLE_SCRIM_FOR_APP_LAUNCH.get();
-            if (scrimEnabled) {
-                int scrimColor = Themes.getAttrColor(mLauncher, R.attr.overviewScrimColor);
-                int scrimColorTrans = ColorUtils.setAlphaComponent(scrimColor, 0);
-                int[] colors = isAppOpening
-                        ? new int[]{scrimColorTrans, scrimColor}
-                        : new int[]{scrimColor, scrimColorTrans};
-                ScrimView scrimView = mLauncher.getScrimView();
-                if (scrimView.getBackground() instanceof ColorDrawable) {
-                    scrimView.setBackgroundColor(colors[0]);
-
-                    ObjectAnimator scrim = ObjectAnimator.ofArgb(scrimView, VIEW_BACKGROUND_COLOR,
-                            colors);
-                    scrim.setDuration(CONTENT_SCRIM_DURATION);
-                    scrim.setInterpolator(DECELERATE_1_5);
-
-                    launcherAnimator.play(scrim);
-                }
-            }
-
             endListener = () -> {
                 viewsToAnimate.forEach(view -> {
                     SCALE_PROPERTY.set(view, 1f);
                     view.setLayerType(View.LAYER_TYPE_NONE, null);
                 });
-                if (scrimEnabled) {
-                    mLauncher.getScrimView().setBackgroundColor(Color.TRANSPARENT);
-                }
                 mLauncher.resumeExpensiveViewUpdates();
             };
         }
@@ -1203,24 +1173,13 @@
      * additional animations.
      */
     private void addRemoteAnimations(RemoteAnimationDefinition definition) {
-        mWallpaperOpenRunner = createWallpaperOpenRunner(false /* fromUnlock */);
+        mWallpaperOpenRunner = new WallpaperOpenLauncherAnimationRunner();
         definition.addRemoteAnimation(WindowManager.TRANSIT_OLD_WALLPAPER_OPEN,
                 WindowConfiguration.ACTIVITY_TYPE_STANDARD,
                 new RemoteAnimationAdapter(
                         new LauncherAnimationRunner(mHandler, mWallpaperOpenRunner,
                                 false /* startAtFrontOfQueue */),
                         CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */));
-
-        if (KEYGUARD_ANIMATION.get()) {
-            mKeyguardGoingAwayRunner = createWallpaperOpenRunner(true /* fromUnlock */);
-            definition.addRemoteAnimation(
-                    WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER,
-                    new RemoteAnimationAdapter(
-                            new LauncherAnimationRunner(
-                                    mHandler, mKeyguardGoingAwayRunner,
-                                    true /* startAtFrontOfQueue */),
-                            CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */));
-        }
     }
 
     /**
@@ -1232,7 +1191,7 @@
             return;
         }
 
-        mWallpaperOpenTransitionRunner = createWallpaperOpenRunner(false /* fromUnlock */);
+        mWallpaperOpenTransitionRunner = new WallpaperOpenLauncherAnimationRunner();
         mLauncherOpenTransition = new RemoteTransition(
                 new LauncherAnimationRunner(mHandler, mWallpaperOpenTransitionRunner,
                         false /* startAtFrontOfQueue */).toRemoteTransition(),
@@ -1287,7 +1246,6 @@
         // definition so we don't have to wait for the system gc
         mWallpaperOpenRunner = null;
         mAppLaunchRunner = null;
-        mKeyguardGoingAwayRunner = null;
     }
 
     protected void unregisterRemoteTransitions() {
@@ -1345,41 +1303,6 @@
         return false;
     }
 
-    /**
-     * @return Runner that plays when user goes to Launcher
-     * ie. pressing home, swiping up from nav bar.
-     */
-    RemoteAnimationFactory createWallpaperOpenRunner(boolean fromUnlock) {
-        return new WallpaperOpenLauncherAnimationRunner(fromUnlock);
-    }
-
-    /**
-     * Animator that controls the transformations of the windows when unlocking the device.
-     */
-    private Animator getUnlockWindowAnimator(RemoteAnimationTarget[] appTargets,
-            RemoteAnimationTarget[] wallpaperTargets) {
-        SurfaceTransactionApplier surfaceApplier = new SurfaceTransactionApplier(mDragLayer);
-        ValueAnimator unlockAnimator = ValueAnimator.ofFloat(0, 1);
-        unlockAnimator.setDuration(CLOSING_TRANSITION_DURATION_MS);
-        float cornerRadius = mDeviceProfile.isMultiWindowMode ? 0 :
-                QuickStepContract.getWindowCornerRadius(mLauncher);
-        unlockAnimator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationStart(Animator animation) {
-                SurfaceTransaction transaction = new SurfaceTransaction();
-                for (int i = appTargets.length - 1; i >= 0; i--) {
-                    RemoteAnimationTarget target = appTargets[i];
-                    transaction.forSurface(target.leash)
-                            .setAlpha(1f)
-                            .setWindowCrop(target.screenSpaceBounds)
-                            .setCornerRadius(cornerRadius);
-                }
-                surfaceApplier.scheduleApply(transaction);
-            }
-        });
-        return unlockAnimator;
-    }
-
     private static int getRotationChange(RemoteAnimationTarget[] appTargets) {
         int rotationChange = 0;
         for (RemoteAnimationTarget target : appTargets) {
@@ -1433,20 +1356,12 @@
 
         // Find the associated item info for the launch cookie (if available), note that predicted
         // apps actually have an id of -1, so use another default id here
-        final ArrayList<IBinder> launchCookies = runningTaskTarget.taskInfo.launchCookies == null
-                ? new ArrayList<>()
+        final List<IBinder> launchCookies = runningTaskTarget.taskInfo.launchCookies == null
+                ? Collections.EMPTY_LIST
                 : runningTaskTarget.taskInfo.launchCookies;
 
-        int launchCookieItemId = NO_MATCHING_ID;
-        for (IBinder cookie : launchCookies) {
-            Integer itemId = ObjectWrapper.unwrap(cookie);
-            if (itemId != null) {
-                launchCookieItemId = itemId;
-                break;
-            }
-        }
-
-        return mLauncher.getFirstMatchForAppClose(launchCookieItemId, packageName,
+        return mLauncher.getFirstMatchForAppClose(
+                StableViewInfo.fromLaunchCookies(launchCookies), packageName,
                 UserHandle.of(runningTaskTarget.taskInfo.userId), true /* supportsAllAppsState */);
     }
 
@@ -1701,7 +1616,6 @@
     public Pair<RectFSpringAnim, AnimatorSet> createWallpaperOpenAnimations(
             RemoteAnimationTarget[] appTargets,
             RemoteAnimationTarget[] wallpaperTargets,
-            boolean fromUnlock,
             RectF startRect,
             float startWindowCornerRadius,
             boolean fromPredictiveBack) {
@@ -1719,10 +1633,7 @@
 
         boolean playWorkspaceReveal = !fromPredictiveBack;
         boolean skipAllAppsScale = false;
-        if (fromUnlock) {
-            anim.play(getUnlockWindowAnimator(appTargets, wallpaperTargets));
-        } else if (ENABLE_BACK_SWIPE_HOME_ANIMATION.get()
-                && !playFallBackAnimation) {
+        if (ENABLE_BACK_SWIPE_HOME_ANIMATION.get() && !playFallBackAnimation) {
             PointF velocity;
             if (enableScalingRevealHomeAnimation()) {
                 velocity = new PointF();
@@ -1834,12 +1745,6 @@
      */
     protected class WallpaperOpenLauncherAnimationRunner implements RemoteAnimationFactory {
 
-        private final boolean mFromUnlock;
-
-        public WallpaperOpenLauncherAnimationRunner(boolean fromUnlock) {
-            mFromUnlock = fromUnlock;
-        }
-
         @Override
         public void onAnimationStart(int transit,
                 RemoteAnimationTarget[] appTargets,
@@ -1871,7 +1776,7 @@
             }
 
             Pair<RectFSpringAnim, AnimatorSet> pair = createWallpaperOpenAnimations(
-                    appTargets, wallpaperTargets, mFromUnlock, resolveRectF,
+                    appTargets, wallpaperTargets, resolveRectF,
                     QuickStepContract.getWindowCornerRadius(mLauncher),
                     false /* fromPredictiveBack */);
 
diff --git a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
index 50e8e5e..955388d 100644
--- a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
+++ b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
@@ -41,6 +41,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.dragndrop.SimpleDragLayer;
+import com.android.launcher3.model.StringCache;
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.model.WidgetPredictionsRequester;
 import com.android.launcher3.model.WidgetsModel;
@@ -107,6 +108,7 @@
     private SimpleDragLayer<WidgetPickerActivity> mDragLayer;
     private WidgetsModel mModel;
     private LauncherAppState mApp;
+    private StringCache mStringCache;
     private WidgetPredictionsRequester mWidgetPredictionsRequester;
     private final WidgetPickerDataProvider mWidgetPickerDataProvider =
             new WidgetPickerDataProvider();
@@ -287,6 +289,11 @@
         MODEL_EXECUTOR.execute(() -> {
             LauncherAppState app = LauncherAppState.getInstance(this);
             mModel.update(app, null);
+
+            StringCache stringCache = new StringCache();
+            stringCache.loadStrings(this);
+
+            bindStringCache(stringCache);
             bindWidgets(mModel.getWidgetsByPackageItem());
             // Open sheet once widgets are available, so that it doesn't interrupt the open
             // animation.
@@ -299,6 +306,10 @@
         });
     }
 
+    private void bindStringCache(final StringCache stringCache) {
+        MAIN_EXECUTOR.execute(() -> mStringCache = stringCache);
+    }
+
     private void bindWidgets(Map<PackageItemInfo, List<WidgetItem>> widgets) {
         WidgetsListBaseEntriesBuilder builder = new WidgetsListBaseEntriesBuilder(
                 mApp.getContext());
@@ -336,6 +347,12 @@
         }
     }
 
+    @Nullable
+    @Override
+    public StringCache getStringCache() {
+        return mStringCache;
+    }
+
     /**
      * Animation callback for different predictive back animation states for the widget picker.
      */
diff --git a/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
index e31b1d4..f29980b 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
@@ -17,10 +17,10 @@
 
 import static android.view.View.VISIBLE;
 
-import static com.android.launcher3.LauncherState.BACKGROUND_APP;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.wm.shell.shared.desktopmode.DesktopModeFlags.WALLPAPER_ACTIVITY;
 
+import android.content.Context;
 import android.os.Debug;
 import android.util.Log;
 import android.view.View;
@@ -30,14 +30,18 @@
 
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.statemanager.BaseState;
 import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
 import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.views.ActivityContext;
 import com.android.quickstep.GestureState;
 import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.fallback.RecentsState;
 import com.android.wm.shell.desktopmode.IDesktopTaskListener;
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 
+import java.io.PrintWriter;
 import java.util.HashSet;
 import java.util.Set;
 
@@ -49,7 +53,6 @@
 
     private static final String TAG = "DesktopVisController";
     private static final boolean DEBUG = false;
-    private final Launcher mLauncher;
     private final Set<DesktopVisibilityListener> mDesktopVisibilityListeners = new HashSet<>();
     private final Set<TaskbarDesktopModeListener> mTaskbarDesktopModeListeners = new HashSet<>();
 
@@ -61,23 +64,43 @@
     @Nullable
     private DesktopTaskListenerImpl mDesktopTaskListener;
 
-    public DesktopVisibilityController(Launcher launcher) {
-        mLauncher = launcher;
+    @Nullable
+    private Context mContext;
+
+    public DesktopVisibilityController(@NonNull Context context) {
+        setContext(context);
     }
 
-    /**
-     * Register a listener with System UI to receive updates about desktop tasks state
-     */
-    public void registerSystemUiListener() {
-        mDesktopTaskListener = new DesktopTaskListenerImpl(this, mLauncher.getDisplayId());
-        SystemUiProxy.INSTANCE.get(mLauncher).setDesktopTaskListener(mDesktopTaskListener);
+    /** Sets the context and re-registers the System Ui listener */
+    private void setContext(@Nullable Context context) {
+        unregisterSystemUiListener();
+        mContext = context;
+        registerSystemUiListener();
+    }
+
+    /** Register a listener with System UI to receive updates about desktop tasks state */
+    private void registerSystemUiListener() {
+        if (mContext == null) {
+            return;
+        }
+        if (mDesktopTaskListener != null) {
+            return;
+        }
+        mDesktopTaskListener = new DesktopTaskListenerImpl(this, mContext.getDisplayId());
+        SystemUiProxy.INSTANCE.get(mContext).setDesktopTaskListener(mDesktopTaskListener);
     }
 
     /**
      * Clear listener from System UI that was set with {@link #registerSystemUiListener()}
      */
-    public void unregisterSystemUiListener() {
-        SystemUiProxy.INSTANCE.get(mLauncher).setDesktopTaskListener(null);
+    private void unregisterSystemUiListener() {
+        if (mContext == null) {
+            return;
+        }
+        if (mDesktopTaskListener == null) {
+            return;
+        }
+        SystemUiProxy.INSTANCE.get(mContext).setDesktopTaskListener(null);
         mDesktopTaskListener.release();
         mDesktopTaskListener = null;
     }
@@ -126,6 +149,9 @@
      * it.
      */
     public void setVisibleDesktopTasksCount(int visibleTasksCount) {
+        if (mContext == null) {
+            return;
+        }
         if (DEBUG) {
             Log.d(TAG, "setVisibleDesktopTasksCount: visibleTasksCount=" + visibleTasksCount
                     + " currentValue=" + mVisibleDesktopTasksCount);
@@ -141,7 +167,7 @@
                 notifyDesktopVisibilityListeners(areDesktopTasksVisibleNow);
             }
 
-            if (!WALLPAPER_ACTIVITY.isEnabled(mLauncher) && wasVisible != isVisible) {
+            if (!WALLPAPER_ACTIVITY.isEnabled(mContext) && wasVisible != isVisible) {
                 // TODO: b/333533253 - Remove after flag rollout
                 if (mVisibleDesktopTasksCount > 0) {
                     setLauncherViewsVisibility(View.INVISIBLE);
@@ -160,19 +186,33 @@
         }
     }
 
+    public void onLauncherStateChanged(LauncherState state) {
+        onLauncherStateChanged(
+                state, state == LauncherState.BACKGROUND_APP, state.isRecentsViewVisible);
+    }
+
+    public void onLauncherStateChanged(RecentsState state) {
+        onLauncherStateChanged(
+                state, state == RecentsState.BACKGROUND_APP, state.isRecentsViewVisible());
+    }
+
     /**
      * Process launcher state change and update launcher view visibility based on desktop state
      */
-    public void onLauncherStateChanged(LauncherState state) {
+    public void onLauncherStateChanged(
+            BaseState<?> state, boolean isBackgroundAppState, boolean isRecentsViewVisible) {
         if (DEBUG) {
             Log.d(TAG, "onLauncherStateChanged: newState=" + state);
         }
-        setBackgroundStateEnabled(state == BACKGROUND_APP);
+        setBackgroundStateEnabled(isBackgroundAppState);
         // Desktop visibility tracks overview and background state separately
-        setOverviewStateEnabled(state != BACKGROUND_APP && state.isRecentsViewVisible);
+        setOverviewStateEnabled(!isBackgroundAppState && isRecentsViewVisible);
     }
 
     private void setOverviewStateEnabled(boolean overviewStateEnabled) {
+        if (mContext == null) {
+            return;
+        }
         if (DEBUG) {
             Log.d(TAG, "setOverviewStateEnabled: enabled=" + overviewStateEnabled
                     + " currentValue=" + mInOverviewState);
@@ -185,7 +225,7 @@
                 notifyDesktopVisibilityListeners(areDesktopTasksVisibleNow);
             }
 
-            if (WALLPAPER_ACTIVITY.isEnabled(mLauncher)) {
+            if (WALLPAPER_ACTIVITY.isEnabled(mContext)) {
                 return;
             }
             // TODO: b/333533253 - Clean up after flag rollout
@@ -203,13 +243,16 @@
     }
 
     private void notifyDesktopVisibilityListeners(boolean areDesktopTasksVisible) {
+        if (mContext == null) {
+            return;
+        }
         if (DEBUG) {
             Log.d(TAG, "notifyDesktopVisibilityListeners: visible=" + areDesktopTasksVisible);
         }
         for (DesktopVisibilityListener listener : mDesktopVisibilityListeners) {
             listener.onDesktopVisibilityChanged(areDesktopTasksVisible);
         }
-        DisplayController.handleInfoChangeForDesktopMode(mLauncher);
+        DisplayController.handleInfoChangeForDesktopMode(mContext);
     }
 
     private void notifyTaskbarDesktopModeListeners(boolean doesAnyTaskRequireTaskbarRounding) {
@@ -295,22 +338,32 @@
      * TODO: b/333533253 - Remove after flag rollout
      */
     private void setLauncherViewsVisibility(int visibility) {
-        if (WALLPAPER_ACTIVITY.isEnabled(mLauncher)) {
+        if (mContext == null) {
+            return;
+        }
+        if (WALLPAPER_ACTIVITY.isEnabled(mContext)) {
             return;
         }
         if (DEBUG) {
             Log.d(TAG, "setLauncherViewsVisibility: visibility=" + visibility + " "
                     + Debug.getCaller());
         }
-        View workspaceView = mLauncher.getWorkspace();
-        if (workspaceView != null) {
-            workspaceView.setVisibility(visibility);
+        if (!(mContext instanceof ActivityContext activity)) {
+            return;
         }
-        View dragLayer = mLauncher.getDragLayer();
+        View dragLayer = activity.getDragLayer();
         if (dragLayer != null) {
             dragLayer.setVisibility(visibility);
         }
-        if (mLauncher instanceof QuickstepLauncher ql && ql.getTaskbarUIController() != null
+        if (!(activity instanceof Launcher launcher)) {
+            return;
+        }
+        View workspaceView = launcher.getWorkspace();
+        if (workspaceView != null) {
+            workspaceView.setVisibility(visibility);
+        }
+        if (launcher instanceof QuickstepLauncher ql
+                && ql.getTaskbarUIController() != null
                 && mVisibleDesktopTasksCount != 0) {
             ql.getTaskbarUIController().onLauncherVisibilityChanged(visibility == VISIBLE);
         }
@@ -320,7 +373,10 @@
      * TODO: b/333533253 - Remove after flag rollout
      */
     private void markLauncherPaused() {
-        if (WALLPAPER_ACTIVITY.isEnabled(mLauncher)) {
+        if (mContext == null) {
+            return;
+        }
+        if (WALLPAPER_ACTIVITY.isEnabled(mContext)) {
             return;
         }
         if (DEBUG) {
@@ -337,7 +393,10 @@
      * TODO: b/333533253 - Remove after flag rollout
      */
     private void markLauncherResumed() {
-        if (WALLPAPER_ACTIVITY.isEnabled(mLauncher)) {
+        if (mContext == null) {
+            return;
+        }
+        if (WALLPAPER_ACTIVITY.isEnabled(mContext)) {
             return;
         }
         if (DEBUG) {
@@ -353,6 +412,22 @@
         }
     }
 
+    public void onDestroy() {
+        setContext(null);
+    }
+
+    public void dumpLogs(String prefix, PrintWriter pw) {
+        pw.println(prefix + "DesktopVisibilityController:");
+
+        pw.println(prefix + "\tmDesktopVisibilityListeners=" + mDesktopVisibilityListeners);
+        pw.println(prefix + "\tmVisibleDesktopTasksCount=" + mVisibleDesktopTasksCount);
+        pw.println(prefix + "\tmInOverviewState=" + mInOverviewState);
+        pw.println(prefix + "\tmBackgroundStateEnabled=" + mBackgroundStateEnabled);
+        pw.println(prefix + "\tmGestureInProgress=" + mGestureInProgress);
+        pw.println(prefix + "\tmDesktopTaskListener=" + mDesktopTaskListener);
+        pw.println(prefix + "\tmContext=" + mContext);
+    }
+
     /** A listener for when the user enters/exits Desktop Mode. */
     public interface DesktopVisibilityListener {
         /**
diff --git a/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java
index 06d9ee6..929e793 100644
--- a/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java
@@ -70,7 +70,6 @@
     @Override
     protected void init(TaskbarControllers taskbarControllers) {
         super.init(taskbarControllers);
-
         mRecentsActivity.setTaskbarUIController(this);
         mRecentsActivity.getStateManager().addStateListener(mStateListener);
     }
@@ -78,6 +77,7 @@
     @Override
     protected void onDestroy() {
         super.onDestroy();
+        getRecentsView().setTaskLaunchListener(null);
         mRecentsActivity.setTaskbarUIController(null);
         mRecentsActivity.getStateManager().removeStateListener(mStateListener);
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
index e4cc6bb..ea432f3 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
@@ -23,9 +23,7 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.launcher3.R;
-import com.android.launcher3.statehandlers.DesktopVisibilityController;
 import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
-import com.android.quickstep.LauncherActivityInterface;
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.util.DesktopTask;
 import com.android.quickstep.util.GroupTask;
@@ -116,10 +114,8 @@
         mQuickSwitchViewController = new KeyboardQuickSwitchViewController(
                 mControllers, overlayContext, keyboardQuickSwitchView, mControllerCallbacks);
 
-        DesktopVisibilityController desktopController =
-                LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
         final boolean onDesktop =
-                desktopController != null && desktopController.areDesktopTasksVisible();
+                mControllers.taskbarDesktopModeController.getAreDesktopTasksVisible();
 
         if (mModel.isTaskListValid(mTaskListChangeId)) {
             // When we are opening the KQS with no focus override, check if the first task is
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 69da7b6..5513599 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -38,14 +38,12 @@
 import com.android.launcher3.logging.InstanceId;
 import com.android.launcher3.logging.InstanceIdSequence;
 import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.statehandlers.DesktopVisibilityController;
 import com.android.launcher3.taskbar.bubbles.BubbleBarController;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.MultiPropertyFactory;
 import com.android.launcher3.util.OnboardingPrefs;
 import com.android.quickstep.HomeVisibilityState;
-import com.android.quickstep.LauncherActivityInterface;
 import com.android.quickstep.RecentsAnimationCallbacks;
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.util.GroupTask;
@@ -128,8 +126,8 @@
 
     @Override
     protected void onDestroy() {
-        super.onDestroy();
         onLauncherVisibilityChanged(false);
+        super.onDestroy();
         mTaskbarLauncherStateController.onDestroy();
 
         mLauncher.setTaskbarUIController(null);
@@ -235,11 +233,8 @@
             return null;
         }
 
-        DesktopVisibilityController desktopController =
-                LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
         if (!WALLPAPER_ACTIVITY.isEnabled(mLauncher)
-                && desktopController != null
-                && desktopController.areDesktopTasksVisible()) {
+                && mControllers.taskbarDesktopModeController.getAreDesktopTasksVisible()) {
             // TODO: b/333533253 - Remove after flag rollout
             isVisible = false;
         }
diff --git a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
index ec710c5..fabf3a5 100644
--- a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
@@ -94,6 +94,7 @@
     // States that affect whether region sampling is enabled or not
     private boolean mIsStashed;
     private boolean mIsLumaSamplingEnabled;
+    private boolean mIsAppTransitionPending;
     private boolean mTaskbarHidden;
 
     private float mTranslationYForSwipe;
@@ -267,6 +268,11 @@
         updateSamplingState();
     }
 
+    public void setIsAppTransitionPending(boolean pending) {
+        mIsAppTransitionPending = pending;
+        updateSamplingState();
+    }
+
     private void updateSamplingState() {
         updateRegionSamplingWindowVisibility();
         if (shouldSample()) {
@@ -278,7 +284,7 @@
     }
 
     private boolean shouldSample() {
-        return mIsStashed && mIsLumaSamplingEnabled;
+        return mIsStashed && mIsLumaSamplingEnabled && !mIsAppTransitionPending;
     }
 
     protected void updateStashedHandleHintScale() {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 5f733b0..0efa949 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -100,6 +100,7 @@
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.popup.PopupContainerWithArrow;
 import com.android.launcher3.popup.PopupDataProvider;
+import com.android.launcher3.statehandlers.DesktopVisibilityController;
 import com.android.launcher3.taskbar.TaskbarAutohideSuspendController.AutohideSuspendFlag;
 import com.android.launcher3.taskbar.TaskbarTranslationController.TransitionCallback;
 import com.android.launcher3.taskbar.allapps.TaskbarAllAppsController;
@@ -140,7 +141,6 @@
 import com.android.launcher3.util.VibratorWrapper;
 import com.android.launcher3.util.ViewCache;
 import com.android.launcher3.views.ActivityContext;
-import com.android.quickstep.LauncherActivityInterface;
 import com.android.quickstep.NavHandle;
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.SystemUiProxy;
@@ -223,7 +223,8 @@
     public TaskbarActivityContext(Context windowContext,
             @Nullable Context navigationBarPanelContext, DeviceProfile launcherDp,
             TaskbarNavButtonController buttonController, ScopedUnfoldTransitionProgressProvider
-            unfoldTransitionProgressProvider) {
+            unfoldTransitionProgressProvider,
+            @NonNull DesktopVisibilityController desktopVisibilityController) {
         super(windowContext);
 
         mNavigationBarPanelContext = navigationBarPanelContext;
@@ -336,17 +337,13 @@
                 new VoiceInteractionWindowController(this),
                 new TaskbarTranslationController(this),
                 new TaskbarSpringOnStashController(this),
-                new TaskbarRecentAppsController(
-                        this,
-                        RecentsModel.INSTANCE.get(this),
-                        LauncherActivityInterface.INSTANCE::getDesktopVisibilityController),
+                new TaskbarRecentAppsController(this, RecentsModel.INSTANCE.get(this)),
                 TaskbarEduTooltipController.newInstance(this),
                 new KeyboardQuickSwitchController(),
-                new TaskbarPinningController(this, () ->
-                        DisplayController.isInDesktopMode(this)),
+                new TaskbarPinningController(this),
                 bubbleControllersOptional,
-                new TaskbarDesktopModeController(
-                        LauncherActivityInterface.INSTANCE::getDesktopVisibilityController));
+                new TaskbarDesktopModeController(desktopVisibilityController));
+
         mLauncherPrefs = LauncherPrefs.get(this);
     }
 
@@ -914,11 +911,16 @@
         mControllers.navbarButtonsViewController.transitionTo(barMode, animate);
     }
 
+    public void appTransitionPending(boolean pending) {
+        mControllers.stashedHandleViewController.setIsAppTransitionPending(pending);
+    }
+
     /**
      * Called when this instance of taskbar is no longer needed
      */
     public void onDestroy() {
         mIsDestroyed = true;
+        mTaskbarFeatureEvaluator.onDestroy();
         setUIController(TaskbarUIController.DEFAULT);
         mControllers.onDestroy();
         if (!enableTaskbarNoRecreate() && !ENABLE_TASKBAR_NAVBAR_UNIFICATION) {
@@ -1203,14 +1205,21 @@
         mControllers.uiController.startSplitSelection(splitSelectSource);
     }
 
+    boolean areDesktopTasksVisible() {
+        return mControllers != null
+                && mControllers.taskbarDesktopModeController.getAreDesktopTasksVisible();
+    }
+
     protected void onTaskbarIconClicked(View view) {
         TaskbarUIController taskbarUIController = mControllers.uiController;
         RecentsView recents = taskbarUIController.getRecentsView();
         boolean shouldCloseAllOpenViews = true;
         Object tag = view.getTag();
         if (tag instanceof GroupTask groupTask) {
-            handleGroupTaskLaunch(groupTask, /* remoteTransition = */ null,
-                    DisplayController.isInDesktopMode(this));
+            handleGroupTaskLaunch(
+                    groupTask,
+                    /* remoteTransition= */ null,
+                    areDesktopTasksVisible());
             mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(true);
         } else if (tag instanceof FolderInfo) {
             // Tapping an expandable folder icon on Taskbar
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
index 4ac7514..d6ce3a4 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
@@ -102,7 +102,7 @@
             shadowAlpha = LIGHT_THEME_SHADOW_ALPHA
         }
 
-        if (DisplayController.isInDesktopMode(context)) {
+        if (context.areDesktopTasksVisible()) {
             fullCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context)
             cornerRadius = fullCornerRadius
         } else {
@@ -200,7 +200,7 @@
                 mapRange(
                         scale,
                         0f,
-                        res.getDimensionPixelSize(R.dimen.transient_taskbar_bottom_margin).toFloat()
+                        res.getDimensionPixelSize(R.dimen.transient_taskbar_bottom_margin).toFloat(),
                     )
                     .toInt()
             shadowBlur =
@@ -245,7 +245,7 @@
                 -mapRange(
                     1f - progress,
                     0f,
-                    if (isAnimatingPinning) 0f else stashedHandleHeight / 2f
+                    if (isAnimatingPinning) 0f else stashedHandleHeight / 2f,
                 )
 
         // Draw shadow.
@@ -255,7 +255,7 @@
             shadowBlur,
             0f,
             keyShadowDistance,
-            setColorAlphaBound(Color.BLACK, Math.round(newShadowAlpha))
+            setColorAlphaBound(Color.BLACK, Math.round(newShadowAlpha)),
         )
         strokePaint.alpha = (paint.alpha * strokeAlpha) / 255
 
@@ -263,7 +263,7 @@
             transientBackgroundBounds.left + halfWidthDelta + hotseatOffsetLeft,
             bottom - newBackgroundHeight + hotseatOffsetTop,
             transientBackgroundBounds.right - halfWidthDelta + hotseatOffsetRight,
-            bottom + hotseatOffsetBottom
+            bottom + hotseatOffsetBottom,
         )
         val horizontalInset = fullWidth * widthInsetPercentage
         lastDrawnTransientRect.inset(horizontalInset, 0f)
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
index 34ab9f0..56fd2bb 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
@@ -25,7 +25,6 @@
 import com.android.launcher3.taskbar.allapps.TaskbarAllAppsController;
 import com.android.launcher3.taskbar.bubbles.BubbleControllers;
 import com.android.launcher3.taskbar.overlay.TaskbarOverlayController;
-import com.android.launcher3.util.DisplayController;
 import com.android.systemui.shared.rotation.RotationButtonController;
 
 import java.io.PrintWriter;
@@ -194,13 +193,17 @@
                 voiceInteractionWindowController
         };
 
-        if (DisplayController.isInDesktopMode(taskbarActivityContext)) {
+        if (taskbarDesktopModeController.getAreDesktopTasksVisible()) {
             mCornerRoundness.updateValue(taskbarDesktopModeController.getTaskbarCornerRoundness(
                     mSharedState.showCornerRadiusInDesktopMode));
         } else {
             mCornerRoundness.updateValue(TaskbarBackgroundRenderer.MAX_ROUNDNESS);
         }
+        onPostInit();
+    }
 
+    @VisibleForTesting
+    public void onPostInit() {
         mAreAllControllersInitialized = true;
         for (Runnable postInitCallback : mPostInitCallbacks) {
             postInitCallback.run();
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDesktopModeController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarDesktopModeController.kt
index a376531..47a35c5 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDesktopModeController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDesktopModeController.kt
@@ -22,18 +22,18 @@
 
 /** Handles Taskbar in Desktop Windowing mode. */
 class TaskbarDesktopModeController(
-    private val desktopVisibilityControllerProvider: () -> DesktopVisibilityController?
+    private val desktopVisibilityController: DesktopVisibilityController
 ) : TaskbarDesktopModeListener {
     private lateinit var taskbarControllers: TaskbarControllers
     private lateinit var taskbarSharedState: TaskbarSharedState
 
-    private val desktopVisibilityController: DesktopVisibilityController?
-        get() = desktopVisibilityControllerProvider()
+    val areDesktopTasksVisible: Boolean
+        get() = desktopVisibilityController.areDesktopTasksVisible()
 
     fun init(controllers: TaskbarControllers, sharedState: TaskbarSharedState) {
         taskbarControllers = controllers
         taskbarSharedState = sharedState
-        desktopVisibilityController?.registerTaskbarDesktopModeListener(this)
+        desktopVisibilityController.registerTaskbarDesktopModeListener(this)
     }
 
     override fun onTaskbarCornerRoundingUpdate(doesAnyTaskRequireTaskbarRounding: Boolean) {
@@ -50,5 +50,5 @@
         }
     }
 
-    fun onDestroy() = desktopVisibilityController?.unregisterTaskbarDesktopModeListener(this)
+    fun onDestroy() = desktopVisibilityController.unregisterTaskbarDesktopModeListener(this)
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
index 5bbf4b2..fc307b2 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
@@ -74,14 +74,12 @@
 import com.android.launcher3.popup.PopupContainerWithArrow;
 import com.android.launcher3.shortcuts.DeepShortcutView;
 import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
-import com.android.launcher3.statehandlers.DesktopVisibilityController;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.views.BubbleTextHolder;
-import com.android.quickstep.LauncherActivityInterface;
 import com.android.quickstep.util.GroupTask;
 import com.android.quickstep.util.LogUtils;
 import com.android.quickstep.util.MultiValueUpdateListener;
@@ -344,12 +342,9 @@
     protected void callOnDragStart() {
         super.callOnDragStart();
         // TODO(297921594) clean it up when taskbar to desktop drag is implemented.
-        DesktopVisibilityController desktopController =
-                LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
-
         // Pre-drag has ended, start the global system drag.
-        if (mDisallowGlobalDrag || (desktopController != null
-                && desktopController.areDesktopTasksVisible())) {
+        if (mDisallowGlobalDrag
+                || mControllers.taskbarDesktopModeController.getAreDesktopTasksVisible()) {
             AbstractFloatingView.closeAllOpenViewsExcept(mActivity, TYPE_TASKBAR_ALL_APPS);
             return;
         }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index 63fae8c..6c6bd71 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -48,7 +48,6 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.statemanager.StateManager;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
-import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
 import com.android.quickstep.RecentsAnimationCallbacks;
 import com.android.quickstep.RecentsAnimationController;
@@ -375,7 +374,7 @@
     private void updateOverviewDragState(LauncherState launcherState) {
         boolean disallowLongClick =
                 FeatureFlags.enableSplitContextually()
-                        ? mLauncher.isSplitSelectionActive()
+                        ? mLauncher.isSplitSelectionActive() || mIsAnimatingToLauncher
                         : launcherState == LauncherState.OVERVIEW_SPLIT_SELECT;
         com.android.launcher3.taskbar.Utilities.setOverviewDragState(
                 mControllers, launcherState.disallowTaskbarGlobalDrag(),
@@ -463,7 +462,8 @@
             controllers.bubbleStashController.setBubblesShowingOnOverview(onOverview);
         });
 
-        mControllers.taskbarStashController.updateStateForFlag(FLAG_IN_OVERVIEW,
+        TaskbarStashController stashController = mControllers.taskbarStashController;
+        stashController.updateStateForFlag(FLAG_IN_OVERVIEW,
                 mLauncherState == LauncherState.OVERVIEW);
 
         AnimatorSet animatorSet = new AnimatorSet();
@@ -495,8 +495,6 @@
                 public void onAnimationStart(Animator animation) {
                     mIsAnimatingToLauncher = isInLauncher;
 
-                    TaskbarStashController stashController =
-                            mControllers.taskbarStashController;
                     if (DEBUG) {
                         Log.d(TAG, "onAnimationStart - FLAG_IN_APP: " + !isInLauncher);
                     }
@@ -512,6 +510,8 @@
 
             // Handle closing open popups when going home/overview
             handleOpenFloatingViews = true;
+        } else {
+            stashController.applyState();
         }
 
         if (handleOpenFloatingViews && isInLauncher) {
@@ -592,7 +592,8 @@
 
         float cornerRoundness = isInLauncher ? 0 : 1;
 
-        if (DisplayController.isInDesktopMode(mLauncher) && mControllers.getSharedState() != null) {
+        if (mControllers.taskbarDesktopModeController.getAreDesktopTasksVisible()
+                && mControllers.getSharedState() != null) {
             cornerRoundness =
                     mControllers.taskbarDesktopModeController.getTaskbarCornerRoundness(
                             mControllers.getSharedState().showCornerRadiusInDesktopMode);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index 8c87fa6..1b4db7a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -60,6 +60,7 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.statehandlers.DesktopVisibilityController;
 import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarNavButtonCallbacks;
 import com.android.launcher3.taskbar.unfold.NonDestroyableScopedUnfoldTransitionProgressProvider;
@@ -209,12 +210,14 @@
                 }
             };
 
+    @NonNull private final DesktopVisibilityController mDesktopVisibilityController;
+
     @SuppressLint("WrongConstant")
     public TaskbarManager(
             Context context,
             AllAppsActionManager allAppsActionManager,
-            TaskbarNavButtonCallbacks navCallbacks) {
-
+            TaskbarNavButtonCallbacks navCallbacks,
+            @NonNull DesktopVisibilityController desktopVisibilityController) {
         Display display =
                 context.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY);
         mContext = context.createWindowContext(display,
@@ -224,6 +227,7 @@
         mNavigationBarPanelContext = ENABLE_TASKBAR_NAVBAR_UNIFICATION
                 ? context.createWindowContext(display, TYPE_NAVIGATION_BAR_PANEL, null)
                 : null;
+        mDesktopVisibilityController = desktopVisibilityController;
         if (enableTaskbarNoRecreate()) {
             mWindowManager = mContext.getSystemService(WindowManager.class);
             mTaskbarRootLayout = new FrameLayout(mContext) {
@@ -470,7 +474,7 @@
             if (enableTaskbarNoRecreate() || mTaskbarActivityContext == null) {
                 mTaskbarActivityContext = new TaskbarActivityContext(mContext,
                         mNavigationBarPanelContext, dp, mNavButtonController,
-                        mUnfoldProgressProvider);
+                        mUnfoldProgressProvider, mDesktopVisibilityController);
             } else {
                 mTaskbarActivityContext.updateDeviceProfile(dp);
             }
@@ -549,7 +553,15 @@
 
     public void transitionTo(@BarTransitions.TransitionMode int barMode,
             boolean animate) {
-        mTaskbarActivityContext.transitionTo(barMode, animate);
+        if (mTaskbarActivityContext != null) {
+            mTaskbarActivityContext.transitionTo(barMode, animate);
+        }
+    }
+
+    public void appTransitionPending(boolean pending) {
+        if (mTaskbarActivityContext != null) {
+            mTaskbarActivityContext.appTransitionPending(pending);
+        }
     }
 
     private boolean isTaskbarEnabled(DeviceProfile deviceProfile) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarPinningController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarPinningController.kt
index 6c9cc64..1867cd0 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarPinningController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarPinningController.kt
@@ -32,10 +32,8 @@
 import java.io.PrintWriter
 
 /** Controls taskbar pinning through a popup view. */
-class TaskbarPinningController(
-    private val context: TaskbarActivityContext,
-    private val isInDesktopModeProvider: () -> Boolean,
-) : TaskbarControllers.LoggableTaskbarController {
+class TaskbarPinningController(private val context: TaskbarActivityContext) :
+    TaskbarControllers.LoggableTaskbarController {
 
     private lateinit var controllers: TaskbarControllers
     private lateinit var taskbarSharedState: TaskbarSharedState
@@ -58,7 +56,7 @@
                     return
                 }
                 val shouldPinTaskbar =
-                    if (isInDesktopModeProvider()) {
+                    if (controllers.taskbarDesktopModeController.areDesktopTasksVisible) {
                         !launcherPrefs.get(TASKBAR_PINNING_IN_DESKTOP_MODE)
                     } else {
                         !launcherPrefs.get(TASKBAR_PINNING)
@@ -119,7 +117,7 @@
             dragLayerController.taskbarBackgroundProgress.animateToValue(animateToValue),
             taskbarViewController.taskbarIconTranslationYForPinning.animateToValue(animateToValue),
             taskbarViewController.taskbarIconScaleForPinning.animateToValue(animateToValue),
-            taskbarViewController.taskbarIconTranslationXForPinning.animateToValue(animateToValue)
+            taskbarViewController.taskbarIconTranslationXForPinning.animateToValue(animateToValue),
         )
 
         animatorSet.interpolator = Interpolators.EMPHASIZED
@@ -134,10 +132,10 @@
     @VisibleForTesting
     fun recreateTaskbarAndUpdatePinningValue() {
         updateIsAnimatingTaskbarPinningAndNotifyTaskbarDragLayer(false)
-        if (isInDesktopModeProvider()) {
+        if (controllers.taskbarDesktopModeController.areDesktopTasksVisible) {
             launcherPrefs.put(
                 TASKBAR_PINNING_IN_DESKTOP_MODE,
-                !launcherPrefs.get(TASKBAR_PINNING_IN_DESKTOP_MODE)
+                !launcherPrefs.get(TASKBAR_PINNING_IN_DESKTOP_MODE),
             )
         } else {
             launcherPrefs.put(TASKBAR_PINNING, !launcherPrefs.get(TASKBAR_PINNING))
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
index 332eb95..2cee77d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
@@ -189,7 +189,7 @@
         // here will reflect in the popup
         ArrayList<SystemShortcut.Factory> shortcuts = new ArrayList<>();
         shortcuts.add(APP_INFO);
-        if (!mControllers.taskbarRecentAppsController.isInDesktopMode()) {
+        if (!mControllers.taskbarDesktopModeController.getAreDesktopTasksVisible()) {
             shortcuts.addAll(mControllers.uiController.getSplitMenuOptions().toList());
         }
         if (com.android.wm.shell.Flags.enableBubbleAnything()) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
index 737d031..72bdafe 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
@@ -21,7 +21,6 @@
 import com.android.launcher3.model.data.ItemInfo
 import com.android.launcher3.model.data.TaskItemInfo
 import com.android.launcher3.model.data.WorkspaceItemInfo
-import com.android.launcher3.statehandlers.DesktopVisibilityController
 import com.android.launcher3.taskbar.TaskbarControllers.LoggableTaskbarController
 import com.android.launcher3.util.CancellableTask
 import com.android.quickstep.RecentsModel
@@ -36,13 +35,8 @@
  * - When in Fullscreen mode: show the N most recent Tasks
  * - When in Desktop Mode: show the currently running (open) Tasks
  */
-class TaskbarRecentAppsController(
-    context: Context,
-    private val recentsModel: RecentsModel,
-    // Pass a provider here instead of the actual DesktopVisibilityController instance since that
-    // instance might not be available when this constructor is called.
-    private val desktopVisibilityControllerProvider: () -> DesktopVisibilityController?,
-) : LoggableTaskbarController {
+class TaskbarRecentAppsController(context: Context, private val recentsModel: RecentsModel) :
+    LoggableTaskbarController {
 
     var canShowRunningApps =
         DesktopModeStatus.canEnterDesktopMode(context) &&
@@ -78,19 +72,16 @@
     var shownTasks: List<GroupTask> = emptyList()
         private set
 
-    private val desktopVisibilityController: DesktopVisibilityController?
-        get() = desktopVisibilityControllerProvider()
-
-    val isInDesktopMode: Boolean
-        get() = desktopVisibilityController?.areDesktopTasksVisible() ?: false
-
     val runningTaskIds: Set<Int>
         /**
          * Returns the task IDs of apps that should be indicated as "running" to the user.
          * Specifically, we return all the open tasks if we are in Desktop mode, else emptySet().
          */
         get() {
-            if (!canShowRunningApps || !isInDesktopMode) {
+            if (
+                !canShowRunningApps ||
+                    !controllers.taskbarDesktopModeController.areDesktopTasksVisible
+            ) {
                 return emptySet()
             }
             val tasks = desktopTask?.tasks ?: return emptySet()
@@ -102,7 +93,10 @@
          * Returns the task IDs for the tasks that should be indicated as "minimized" to the user.
          */
         get() {
-            if (!canShowRunningApps || !isInDesktopMode) {
+            if (
+                !canShowRunningApps ||
+                    !controllers.taskbarDesktopModeController.areDesktopTasksVisible
+            ) {
                 return emptySet()
             }
             val desktopTasks = desktopTask?.tasks ?: return emptySet()
@@ -124,7 +118,7 @@
         controllers = taskbarControllers
         if (canShowRunningApps || canShowRecentApps) {
             recentsModel.registerRecentTasksChangedListener(recentTasksChangedListener)
-            reloadRecentTasksIfNeeded()
+            controllers.runAfterInit { reloadRecentTasksIfNeeded() }
         }
     }
 
@@ -137,8 +131,10 @@
     /** Called to update hotseatItems, in order to de-dupe them from Recent/Running tasks later. */
     fun updateHotseatItemInfos(hotseatItems: Array<ItemInfo?>): Array<ItemInfo?> {
         // Ignore predicted apps - we show running or recent apps instead.
+        val areDesktopTasksVisible = controllers.taskbarDesktopModeController.areDesktopTasksVisible
         val removePredictions =
-            (isInDesktopMode && canShowRunningApps) || (!isInDesktopMode && canShowRecentApps)
+            (areDesktopTasksVisible && canShowRunningApps) ||
+                (!areDesktopTasksVisible && canShowRecentApps)
         if (!removePredictions) {
             shownHotseatItems = hotseatItems.filterNotNull()
             onRecentsOrHotseatChanged()
@@ -150,11 +146,11 @@
                 .filter { itemInfo -> !itemInfo.isPredictedItem }
                 .toMutableList()
 
-        if (isInDesktopMode && canShowRunningApps) {
+        if (areDesktopTasksVisible && canShowRunningApps) {
             shownHotseatItems =
                 updateHotseatItemsFromRunningTasks(
                     getOrderedAndWrappedDesktopTasks(),
-                    shownHotseatItems
+                    shownHotseatItems,
                 )
         }
 
@@ -199,7 +195,7 @@
         val oldShownTasks = shownTasks
         orderedRunningTaskIds = updateOrderedRunningTaskIds()
         shownTasks =
-            if (isInDesktopMode) {
+            if (controllers.taskbarDesktopModeController.areDesktopTasksVisible) {
                 computeShownRunningTasks()
             } else {
                 computeShownRecentTasks()
@@ -281,7 +277,7 @@
 
     private fun dedupeHotseatTasks(
         groupTasks: List<GroupTask>,
-        shownHotseatItems: List<ItemInfo>
+        shownHotseatItems: List<ItemInfo>,
     ): List<GroupTask> {
         val hotseatPackages = shownHotseatItems.map { item -> item.targetPackage }
         return groupTasks.filter { groupTask ->
@@ -296,7 +292,7 @@
      */
     private fun updateHotseatItemsFromRunningTasks(
         groupTasks: List<GroupTask>,
-        shownHotseatItems: List<ItemInfo>
+        shownHotseatItems: List<ItemInfo>,
     ): List<ItemInfo> =
         shownHotseatItems.map { itemInfo ->
             if (itemInfo is TaskItemInfo) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index 60e65b3..c1ed39a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -63,10 +63,8 @@
 import com.android.launcher3.R;
 import com.android.launcher3.anim.AnimatedFloat;
 import com.android.launcher3.anim.AnimatorListeners;
-import com.android.launcher3.statehandlers.DesktopVisibilityController;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
-import com.android.quickstep.LauncherActivityInterface;
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.util.SystemUiFlagUtils;
 
@@ -576,6 +574,7 @@
                 /* isStashed= */ mActivity.isPhoneMode(),
                 placeholderDuration,
                 TRANSITION_UNSTASH_SUW_MANUAL,
+                /* skipTaskbarBackgroundDelay */ false,
                 /* jankTag= */ "SUW_MANUAL");
         animation.addListener(AnimatorListeners.forEndCallback(
                 () -> mControllers.taskbarViewController.setDeferUpdatesForSUW(false)));
@@ -585,13 +584,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 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 skipTaskbarBackgroundDelay Iff true, skips delaying the taskbar background.
+     * @param jankTag                    tag to be used in jank monitor trace.
      */
     private void createAnimToIsStashed(boolean isStashed, long duration,
-            @StashAnimation int animationType, String jankTag) {
+            @StashAnimation int animationType, boolean skipTaskbarBackgroundDelay, 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");
@@ -629,7 +629,8 @@
         }
 
         if (isTransientTaskbar) {
-            createTransientAnimToIsStashed(mAnimator, isStashed, duration, animationType);
+            createTransientAnimToIsStashed(mAnimator, isStashed, duration,
+                    skipTaskbarBackgroundDelay, animationType);
         } else {
             createAnimToIsStashed(mAnimator, isStashed, duration, stashTranslation, animationType);
         }
@@ -735,7 +736,7 @@
     }
 
     private void createTransientAnimToIsStashed(AnimatorSet as, boolean isStashed, long duration,
-            @StashAnimation int animationType) {
+            boolean skipTaskbarBackgroundDelay, @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;
@@ -786,7 +787,7 @@
                 backgroundAndHandleAlphaStartDelay,
                 backgroundAndHandleAlphaDuration, LINEAR);
 
-        if (enableScalingRevealHomeAnimation() && !isStashed) {
+        if (enableScalingRevealHomeAnimation() && isStashed && !skipTaskbarBackgroundDelay) {
             play(as, getTaskbarBackgroundAnimatorWhenNotGoingHome(duration),
                     0, 0, LINEAR);
             as.addListener(AnimatorListeners.forEndCallback(
@@ -1079,10 +1080,9 @@
         }
 
         // Do not stash if hardware keyboard is attached, in 3 button nav and desktop windowing mode
-        DesktopVisibilityController visibilityController =
-                LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
-        if (visibilityController != null && mActivity.isHardwareKeyboard()
-                && mActivity.isThreeButtonNav() && visibilityController.areDesktopTasksVisible()) {
+        if (mActivity.isHardwareKeyboard()
+                && mActivity.isThreeButtonNav()
+                && mControllers.taskbarDesktopModeController.getAreDesktopTasksVisible()) {
             return false;
         }
 
@@ -1137,6 +1137,10 @@
                     TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_TRANSIENT_TASKBAR,
                     !hasAnyFlag(FLAG_STASHED_IN_APP_AUTO));
         }
+        if (hasAnyFlag(changedFlags, FLAG_IN_OVERVIEW | FLAG_IN_APP)) {
+            mControllers.runAfterInit(() -> mControllers.taskbarInsetsController
+                    .onTaskbarOrBubblebarWindowHeightOrInsetsChanged());
+        }
         mActivity.applyForciblyShownFlagWhileTransientTaskbarUnstashed(!isStashedInApp());
     }
 
@@ -1176,6 +1180,12 @@
      * Clean up on destroy from TaskbarControllers
      */
     public void onDestroy() {
+        // If the controller is destroyed before the animation finishes, we cancel the animation
+        // so that we don't finish the CUJ.
+        if (mAnimator != null) {
+            mAnimator.cancel();
+            mAnimator = null;
+        }
         UI_HELPER_EXECUTOR.execute(
                 () -> mAccessibilityManager.unregisterSystemAction(SYSTEM_ACTION_ID_TASKBAR));
     }
@@ -1339,8 +1349,10 @@
                 mIsStashed = isStashed;
                 mLastStartedTransitionType = animationType;
 
+                boolean skipTaskbarBgDelay = !hasAnyFlag(FLAG_STASHED_IN_TASKBAR_ALL_APPS)
+                        && hasAnyFlag(FLAG_STASHED_IN_TASKBAR_ALL_APPS, changedFlags);
                 // This sets mAnimator.
-                createAnimToIsStashed(mIsStashed, duration, animationType,
+                createAnimToIsStashed(mIsStashed, duration, animationType, skipTaskbarBgDelay,
                         computeTaskbarJankMonitorTag(changedFlags));
                 return mAnimator;
             }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index aa3e6bf..292b9ed 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -206,7 +206,8 @@
         mModelCallbacks.init(controllers);
         if (mActivity.isUserSetupComplete() && sEnableModelLoadingForTests) {
             // Only load the callbacks if user setup is completed
-            LauncherAppState.getInstance(mActivity).getModel().addCallbacksAndLoad(mModelCallbacks);
+            controllers.runAfterInit(() -> LauncherAppState.getInstance(mActivity).getModel()
+                    .addCallbacksAndLoad(mModelCallbacks));
         }
         mTaskbarNavButtonTranslationY =
                 controllers.navbarButtonsViewController.getTaskbarNavButtonTranslationY();
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt
index 25939e1..f08318e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt
@@ -71,6 +71,27 @@
             }
         }
 
+    /**
+     * Scale of the background in the x direction. Pivot is at the left edge if [anchorLeft] is
+     * `true` and at the right edge if it is `false`
+     */
+    var scaleX: Float = 1f
+        set(value) {
+            if (field != value) {
+                field = value
+                invalidateSelf()
+            }
+        }
+
+    /** Scale of the background in the y direction. Pivot is at the bottom edge. */
+    var scaleY: Float = 1f
+        set(value) {
+            if (field != value) {
+                field = value
+                invalidateSelf()
+            }
+        }
+
     init {
         val res = context.resources
         // configure fill paint
@@ -123,13 +144,17 @@
         )
         // Create background path
         val backgroundPath = Path()
-        val topOffset = backgroundHeight - bounds.height().toFloat()
+        val scaledBackgroundHeight = backgroundHeight * scaleY
+        val scaledWidth = width * scaleX
+        val topOffset = scaledBackgroundHeight - bounds.height().toFloat()
         val radius = backgroundHeight / 2f
-        val left = bounds.left + (if (anchorLeft) 0f else bounds.width().toFloat() - width)
-        val right = bounds.left + (if (anchorLeft) width else bounds.width().toFloat())
-        val top = bounds.top - topOffset + arrowVisibleHeight
 
-        val bottom = bounds.top + bounds.height().toFloat()
+        val left = bounds.left + (if (anchorLeft) 0f else bounds.width().toFloat() - scaledWidth)
+        val right = bounds.left + (if (anchorLeft) scaledWidth else bounds.width().toFloat())
+        // Calculate top with scaled heights for background and arrow to align with stash handle
+        val top = bounds.bottom - scaledBackgroundHeight + getScaledArrowVisibleHeight()
+        val bottom = bounds.bottom.toFloat()
+
         backgroundPath.addRoundRect(left, top, right, bottom, radius, radius, Path.Direction.CW)
         addArrowPathIfNeeded(backgroundPath, topOffset)
 
@@ -142,19 +167,20 @@
     private fun addArrowPathIfNeeded(sourcePath: Path, topOffset: Float) {
         if (!showingArrow || arrowHeightFraction <= 0) return
         val arrowPath = Path()
+        val scaledHeight = getScaledArrowHeight()
         RoundedArrowDrawable.addDownPointingRoundedTriangleToPath(
             arrowWidth,
-            arrowHeight,
+            scaledHeight,
             arrowTipRadius,
             arrowPath
         )
         // flip it horizontally
         val pathTransform = Matrix()
-        pathTransform.setRotate(180f, arrowWidth * 0.5f, arrowHeight * 0.5f)
+        pathTransform.setRotate(180f, arrowWidth * 0.5f, scaledHeight * 0.5f)
         arrowPath.transform(pathTransform)
         // shift to arrow position
         val arrowStart = bounds.left + arrowPositionX - (arrowWidth / 2f)
-        val arrowTop = (1 - arrowHeightFraction) * arrowVisibleHeight - topOffset
+        val arrowTop = (1 - arrowHeightFraction) * getScaledArrowVisibleHeight() - topOffset
         arrowPath.offset(arrowStart, arrowTop)
         // union with rectangle
         sourcePath.op(arrowPath, Path.Op.UNION)
@@ -183,6 +209,7 @@
 
     fun setBackgroundHeight(newHeight: Float) {
         backgroundHeight = newHeight
+        invalidateSelf()
     }
 
     /**
@@ -199,6 +226,14 @@
         invalidateSelf()
     }
 
+    private fun getScaledArrowHeight(): Float {
+        return arrowHeight * scaleY
+    }
+
+    private fun getScaledArrowVisibleHeight(): Float {
+        return max(0f, getScaledArrowHeight() - (arrowHeight - arrowVisibleHeight))
+    }
+
     companion object {
         private const val DARK_THEME_STROKE_ALPHA = 51
         private const val LIGHT_THEME_STROKE_ALPHA = 41
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
index 06301c7..14f6e3a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
@@ -205,7 +205,6 @@
 
     public BubbleBarView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
-        setAlpha(0);
         setVisibility(INVISIBLE);
         mIconOverlapAmount = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_overlap);
         mBubbleBarPadding = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_spacing);
@@ -306,6 +305,36 @@
     }
 
     /**
+     * Set scale for bubble bar background in x direction
+     */
+    public void setBackgroundScaleX(float scaleX) {
+        mBubbleBarBackground.setScaleX(scaleX);
+    }
+
+    /**
+     * Set scale for bubble bar background in y direction
+     */
+    public void setBackgroundScaleY(float scaleY) {
+        mBubbleBarBackground.setScaleY(scaleY);
+    }
+
+    /**
+     * Set alpha for bubble views
+     */
+    public void setBubbleAlpha(float alpha) {
+        for (int i = 0; i < getChildCount(); i++) {
+            getChildAt(i).setAlpha(alpha);
+        }
+    }
+
+    /**
+     * Set alpha for bar background
+     */
+    public void setBackgroundAlpha(float alpha) {
+        mBubbleBarBackground.setAlpha((int) (255 * alpha));
+    }
+
+    /**
      * Sets new icon sizes and newBubbleBarPadding between icons and bubble bar borders.
      *
      * @param newIconSize         new icon size
@@ -322,7 +351,7 @@
         int childCount = getChildCount();
         for (int i = 0; i < childCount; i++) {
             View childView = getChildAt(i);
-            childView.setScaleY(mIconScale);
+            childView.setScaleX(mIconScale);
             childView.setScaleY(mIconScale);
             FrameLayout.LayoutParams params = (LayoutParams) childView.getLayoutParams();
             params.height = (int) mIconSize;
@@ -770,6 +799,9 @@
             removeView(removedBubble);
             int index = addingOverflow ? getChildCount() : 0;
             addView(addedBubble, index, lp);
+            if (onEndRunnable != null) {
+                onEndRunnable.run();
+            }
             return;
         }
         int index = addingOverflow ? getChildCount() : 0;
@@ -1015,7 +1047,7 @@
                 // where the bubble will end up when the animation ends
                 final float targetX = expandedX + expandedBarShift;
                 bv.setTranslationX(widthState * (targetX - collapsedX) + collapsedX);
-                bv.setAlpha(1);
+                bv.setVisibility(VISIBLE);
             } else {
                 // If bar is on the right, account for bubble bar expanding and shifting left
                 final float collapsedBarShift = onLeft ? 0 : currentWidth - collapsedWidth;
@@ -1025,9 +1057,9 @@
                 // the overflow.
                 if (widthState == 0) {
                     if (bv.isOverflow() || i > MAX_VISIBLE_BUBBLES_COLLAPSED - 1) {
-                        bv.setAlpha(0);
+                        bv.setVisibility(INVISIBLE);
                     } else {
-                        bv.setAlpha(1);
+                        bv.setVisibility(VISIBLE);
                     }
                 }
             }
@@ -1335,7 +1367,7 @@
      * touch bounds.
      */
     public boolean isEventOverAnyItem(MotionEvent ev) {
-        if (getVisibility() == View.VISIBLE) {
+        if (getVisibility() == VISIBLE) {
             getBoundsOnScreen(mTempRect);
             return mTempRect.contains((int) ev.getX(), (int) ev.getY());
         }
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
index d9e3406..570b1b9 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
@@ -18,6 +18,11 @@
 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;
@@ -34,6 +39,7 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.AnimatedFloat;
+import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
 import com.android.launcher3.taskbar.TaskbarActivityContext;
 import com.android.launcher3.taskbar.TaskbarControllers;
 import com.android.launcher3.taskbar.TaskbarInsetsController;
@@ -80,8 +86,15 @@
 
     // These are exposed to {@link BubbleStashController} to animate for stashing/un-stashing
     private final MultiValueAlpha mBubbleBarAlpha;
+    private final AnimatedFloat mBubbleBarBubbleAlpha = new AnimatedFloat(this::updateBubbleAlpha);
+    private final AnimatedFloat mBubbleBarBackgroundAlpha = new AnimatedFloat(
+            this::updateBackgroundAlpha);
     private final AnimatedFloat mBubbleBarScaleX = new AnimatedFloat(this::updateScaleX);
     private final AnimatedFloat mBubbleBarScaleY = new AnimatedFloat(this::updateScaleY);
+    private final AnimatedFloat mBubbleBarBackgroundScaleX = new AnimatedFloat(
+            this::updateBackgroundScaleX);
+    private final AnimatedFloat mBubbleBarBackgroundScaleY = new AnimatedFloat(
+            this::updateBackgroundScaleY);
     private final AnimatedFloat mBubbleBarTranslationY = new AnimatedFloat(
             this::updateTranslationY);
 
@@ -258,6 +271,14 @@
         return mBubbleBarAlpha;
     }
 
+    public AnimatedFloat getBubbleBarBubbleAlpha() {
+        return mBubbleBarBubbleAlpha;
+    }
+
+    public AnimatedFloat getBubbleBarBackgroundAlpha() {
+        return mBubbleBarBackgroundAlpha;
+    }
+
     public AnimatedFloat getBubbleBarScaleX() {
         return mBubbleBarScaleX;
     }
@@ -266,6 +287,14 @@
         return mBubbleBarScaleY;
     }
 
+    public AnimatedFloat getBubbleBarBackgroundScaleX() {
+        return mBubbleBarBackgroundScaleX;
+    }
+
+    public AnimatedFloat getBubbleBarBackgroundScaleY() {
+        return mBubbleBarBackgroundScaleY;
+    }
+
     public AnimatedFloat getBubbleBarTranslationY() {
         return mBubbleBarTranslationY;
     }
@@ -535,6 +564,22 @@
         mBarView.setScaleY(scale);
     }
 
+    private void updateBackgroundScaleX(float scale) {
+        mBarView.setBackgroundScaleX(scale);
+    }
+
+    private void updateBackgroundScaleY(float scale) {
+        mBarView.setBackgroundScaleY(scale);
+    }
+
+    private void updateBubbleAlpha(float alpha) {
+        mBarView.setBubbleAlpha(alpha);
+    }
+
+    private void updateBackgroundAlpha(float alpha) {
+        mBarView.setBackgroundAlpha(alpha);
+    }
+
     //
     // Manipulating the specific bubble views in the bar
     //
@@ -820,6 +865,53 @@
     }
 
     /**
+     * Create an animator for showing or hiding bubbles when stashed state changes
+     *
+     * @param isStashed {@code true} when bubble bar should be stashed to the handle
+     */
+    public Animator createRevealAnimatorForStashChange(boolean isStashed) {
+        Rect stashedHandleBounds = new Rect();
+        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));
+        }
+        return animatorSet;
+    }
+
+    private Animator createRevealAnimForBubble(BubbleView bubbleView, boolean isStashed,
+            Rect stashedHandleBounds, float newWidth) {
+        Rect viewBounds = new Rect(0, 0, bubbleView.getWidth(), bubbleView.getHeight());
+
+        int viewCenterY = viewBounds.centerY();
+        int halfHandleHeight = stashedHandleBounds.height() / 2;
+        int widthDelta = Math.max(0, (int) (viewBounds.width() - newWidth) / 2);
+
+        Rect stashedViewBounds = new Rect(
+                viewBounds.left + widthDelta,
+                viewCenterY - halfHandleHeight,
+                viewBounds.right - widthDelta,
+                viewCenterY + halfHandleHeight
+        );
+
+        float viewRadius = 0f; // Use 0 to not clip the new message dot or the app icon
+        float stashedRadius = stashedViewBounds.height() / 2f;
+
+        return new RoundedRectRevealOutlineProvider(viewRadius, stashedRadius, viewBounds,
+                stashedViewBounds).createRevealAnimator(bubbleView, !isStashed, 0);
+    }
+
+    /**
      * Listener to receive updates about bubble bar bounds changing
      */
     public interface BubbleBarBoundsChangeListener {
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java
index fdd385a..3640c3b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java
@@ -187,6 +187,13 @@
     }
 
     /**
+     * Returns bounds of the stashed handle view
+     */
+    public void getBounds(Rect bounds) {
+        bounds.set(mStashedHandleBounds);
+    }
+
+    /**
      * Called when system ui state changes. Bubbles don't show when the device is locked.
      */
     public void setHiddenForSysui(boolean hidden) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
index 591a9da..cc6b49a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
@@ -20,12 +20,14 @@
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
+import android.graphics.Color;
 import android.graphics.Path;
 import android.graphics.Rect;
 import android.graphics.drawable.BitmapDrawable;
 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;
@@ -48,12 +50,27 @@
 
     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;
@@ -110,6 +127,10 @@
 
         setFocusable(true);
         setClickable(true);
+
+        // We manage the shadow ourselves when creating the bitmap
+        setOutlineAmbientShadowColor(Color.TRANSPARENT);
+        setOutlineSpotShadowColor(Color.TRANSPARENT);
     }
 
     private void updateBubbleSizeAndDotRender() {
@@ -152,16 +173,34 @@
         applyDragTranslation();
     }
 
+    private void applyDragTranslation() {
+        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);
         updateBubbleSizeAndDotRender();
     }
 
-    private void applyDragTranslation() {
-        setTranslationX(mDragTranslationX + mOffsetX);
-    }
-
     @Override
     public void dispatchDraw(Canvas canvas) {
         super.dispatchDraw(canvas);
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt
index 99c50f2..6a955d9 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt
@@ -169,6 +169,8 @@
         bubbleBarView.translationY = 0f
         bubbleBarView.scaleX = 1f
         bubbleBarView.scaleY = BUBBLE_ANIMATION_INITIAL_SCALE_Y
+        bubbleBarView.setBackgroundScaleX(1f)
+        bubbleBarView.setBackgroundScaleY(1f)
         bubbleBarView.relativePivotY = 0.5f
 
         // this is the offset between the center of the bubble bar and the center of the stash
@@ -311,6 +313,7 @@
             animatingBubble = null
             if (!canceled) bubbleStashController.stashBubbleBarImmediate()
             bubbleBarView.relativePivotY = 1f
+            bubbleBarView.scaleY = 1f
             bubbleStashController.updateTaskbarTouchRegion()
         }
         animator.start()
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/BubbleStashController.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/BubbleStashController.kt
index 8d63217..9721792 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/BubbleStashController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/BubbleStashController.kt
@@ -16,6 +16,7 @@
 
 package com.android.launcher3.taskbar.bubbles.stashing
 
+import android.graphics.Rect
 import android.view.InsetsController
 import android.view.MotionEvent
 import android.view.View
@@ -146,6 +147,9 @@
     /** Returns the translation of the handle. */
     fun getHandleTranslationY(): Float?
 
+    /** Returns bounds of the handle */
+    fun getHandleBounds(bounds: Rect)
+
     /**
      * Returns bubble bar Y position according to [isBubblesShowingOnHome] and
      * [isBubblesShowingOnOverview] values. Default implementation only analyse
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentBubbleStashController.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentBubbleStashController.kt
index eaf4bf9..7d6f7ad 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentBubbleStashController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentBubbleStashController.kt
@@ -19,6 +19,7 @@
 import android.animation.Animator
 import android.animation.AnimatorListenerAdapter
 import android.animation.AnimatorSet
+import android.graphics.Rect
 import android.view.MotionEvent
 import android.view.View
 import com.android.launcher3.anim.AnimatedFloat
@@ -200,6 +201,10 @@
 
     override fun getHandleTranslationY(): Float? = null
 
+    override fun getHandleBounds(bounds: Rect) {
+        // no op since does not have a handle view
+    }
+
     private fun updateExpandedState(expand: Boolean) {
         if (bubbleBarViewController.isHiddenForNoBubbles) {
             // If there are no bubbles the bar is invisible, nothing to do here.
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 1157305..5d1e890 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt
@@ -19,6 +19,7 @@
 import android.animation.Animator
 import android.animation.AnimatorSet
 import android.content.Context
+import android.graphics.Rect
 import android.view.MotionEvent
 import android.view.View
 import androidx.annotation.VisibleForTesting
@@ -66,9 +67,11 @@
 
     // bubble bar properties
     private lateinit var bubbleBarAlpha: MultiPropertyFactory<View>.MultiProperty
+    private lateinit var bubbleBarBubbleAlpha: AnimatedFloat
+    private lateinit var bubbleBarBackgroundAlpha: AnimatedFloat
     private lateinit var bubbleBarTranslationYAnimator: AnimatedFloat
-    private lateinit var bubbleBarScaleX: AnimatedFloat
-    private lateinit var bubbleBarScaleY: AnimatedFloat
+    private lateinit var bubbleBarBackgroundScaleX: AnimatedFloat
+    private lateinit var bubbleBarBackgroundScaleY: AnimatedFloat
     private val handleCenterFromScreenBottom =
         context.resources.getDimensionPixelSize(R.dimen.bubblebar_stashed_size) / 2f
 
@@ -149,8 +152,10 @@
         bubbleBarTranslationYAnimator = bubbleBarViewController.bubbleBarTranslationY
         // bubble bar has only alpha property, getting it at index 0
         bubbleBarAlpha = bubbleBarViewController.bubbleBarAlpha.get(/* index= */ 0)
-        bubbleBarScaleX = bubbleBarViewController.bubbleBarScaleX
-        bubbleBarScaleY = bubbleBarViewController.bubbleBarScaleY
+        bubbleBarBubbleAlpha = bubbleBarViewController.bubbleBarBubbleAlpha
+        bubbleBarBackgroundAlpha = bubbleBarViewController.bubbleBarBackgroundAlpha
+        bubbleBarBackgroundScaleX = bubbleBarViewController.bubbleBarBackgroundScaleX
+        bubbleBarBackgroundScaleY = bubbleBarViewController.bubbleBarBackgroundScaleY
         stashedHeight = bubbleStashedHandleViewController?.stashedHeight ?: 0
         stashHandleViewAlpha = bubbleStashedHandleViewController?.stashedHandleAlpha?.get(0)
     }
@@ -160,10 +165,12 @@
         if (isBubblesShowingOnHome || isBubblesShowingOnOverview) {
             isStashed = false
             animatorSet.playTogether(
-                bubbleBarScaleX.animateToValue(1f),
-                bubbleBarScaleY.animateToValue(1f),
+                bubbleBarBackgroundScaleX.animateToValue(1f),
+                bubbleBarBackgroundScaleY.animateToValue(1f),
                 bubbleBarTranslationYAnimator.animateToValue(bubbleBarTranslationY),
-                bubbleBarAlpha.animateToValue(1f)
+                bubbleBarAlpha.animateToValue(1f),
+                bubbleBarBubbleAlpha.animateToValue(1f),
+                bubbleBarBackgroundAlpha.animateToValue(1f)
             )
         } else {
             isStashed = true
@@ -181,8 +188,10 @@
         stashHandleViewAlpha?.value = 0f
         this.bubbleBarTranslationYAnimator.updateValue(bubbleBarTranslationY)
         bubbleBarAlpha.setValue(1f)
-        bubbleBarScaleX.updateValue(1f)
-        bubbleBarScaleY.updateValue(1f)
+        bubbleBarBubbleAlpha.updateValue(1f)
+        bubbleBarBackgroundAlpha.updateValue(1f)
+        bubbleBarBackgroundScaleX.updateValue(1f)
+        bubbleBarBackgroundScaleY.updateValue(1f)
         isStashed = false
         onIsStashedChanged()
     }
@@ -192,8 +201,11 @@
         stashHandleViewAlpha?.value = 1f
         this.bubbleBarTranslationYAnimator.updateValue(getStashTranslation())
         bubbleBarAlpha.setValue(0f)
-        bubbleBarScaleX.updateValue(getStashScaleX())
-        bubbleBarScaleY.updateValue(getStashScaleY())
+        // Reset bubble and background alpha to 1 and only keep the bubble bar alpha at 0
+        bubbleBarBubbleAlpha.updateValue(1f)
+        bubbleBarBackgroundAlpha.updateValue(1f)
+        bubbleBarBackgroundScaleX.updateValue(getStashScaleX())
+        bubbleBarBackgroundScaleY.updateValue(getStashScaleY())
         isStashed = true
         onIsStashedChanged()
     }
@@ -258,8 +270,12 @@
 
     override fun getHandleTranslationY(): Float? = bubbleStashedHandleViewController?.translationY
 
+    override fun getHandleBounds(bounds: Rect) {
+        bubbleStashedHandleViewController?.getBounds(bounds)
+    }
+
     private fun getStashTranslation(): Float {
-        return bubbleBarTranslationY / 2f
+        return (bubbleBarTranslationY - stashedHeight) / 2f
     }
 
     @VisibleForTesting
@@ -288,13 +304,22 @@
         val alphaDuration = if (isStashed) duration else TASKBAR_STASH_ALPHA_DURATION
         val alphaDelay = if (isStashed) TASKBAR_STASH_ALPHA_START_DELAY else 0L
         animatorSet.play(
-            createStashAlphaAnimator(isStashed).apply {
+            createBackgroundAlphaAnimator(isStashed).apply {
                 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
+            }
+        )
+
         animatorSet.play(
             createSpringOnStashAnimator(isStashed).apply {
                 this.duration = duration
@@ -303,6 +328,13 @@
         )
 
         animatorSet.play(
+            bubbleBarViewController.createRevealAnimatorForStashChange(isStashed).apply {
+                this.duration = duration
+                this.interpolator = EMPHASIZED
+            }
+        )
+
+        animatorSet.play(
             bubbleStashedHandleViewController?.createRevealAnimToIsStashed(isStashed)?.apply {
                 this.duration = duration
                 this.interpolator = EMPHASIZED
@@ -326,10 +358,21 @@
             }
         )
 
+        animatorSet.doOnStart {
+            if (!isStashed) {
+                bubbleBarBackgroundAlpha.updateValue(0f)
+                bubbleBarBubbleAlpha.updateValue(0f)
+                bubbleBarAlpha.value = 1f
+            }
+        }
         animatorSet.doOnEnd {
             animator = null
             controllersAfterInitAction.runAfterInit {
                 if (isStashed) {
+                    bubbleBarAlpha.value = 0f
+                    // reset bubble view alpha
+                    bubbleBarBubbleAlpha.updateValue(1f)
+                    bubbleBarBackgroundAlpha.updateValue(1f)
                     bubbleBarViewController.isExpanded = false
                 }
                 taskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged()
@@ -338,11 +381,11 @@
         return animatorSet
     }
 
-    private fun createStashAlphaAnimator(isStashed: Boolean): AnimatorSet {
+    private fun createBackgroundAlphaAnimator(isStashed: Boolean): AnimatorSet {
         val stashHandleAlphaTarget = if (isStashed) 1f else 0f
         val barAlphaTarget = if (isStashed) 0f else 1f
         return AnimatorSet().apply {
-            play(bubbleBarAlpha.animateToValue(barAlphaTarget))
+            play(bubbleBarBackgroundAlpha.animateToValue(barAlphaTarget))
             play(stashHandleViewAlpha?.animateToValue(stashHandleAlphaTarget))
         }
     }
@@ -366,8 +409,8 @@
         val scaleXTarget = if (isStashed) getStashScaleX() else 1f
         val scaleYTarget = if (isStashed) getStashScaleY() else 1f
         return AnimatorSet().apply {
-            play(bubbleBarScaleX.animateToValue(scaleXTarget))
-            play(bubbleBarScaleY.animateToValue(scaleYTarget))
+            play(bubbleBarBackgroundScaleX.animateToValue(scaleXTarget))
+            play(bubbleBarBackgroundScaleY.animateToValue(scaleYTarget))
         }
     }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarFeatureEvaluator.kt b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarFeatureEvaluator.kt
index c83ac50..7739a0e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarFeatureEvaluator.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarFeatureEvaluator.kt
@@ -26,23 +26,6 @@
 private constructor(
     private val taskbarActivityContext: TaskbarActivityContext,
 ) {
-
-    companion object {
-        @Volatile private lateinit var taskbarFeatureEvaluator: TaskbarFeatureEvaluator
-
-        @JvmStatic
-        fun getInstance(
-            taskbarActivityContext: TaskbarActivityContext,
-        ): TaskbarFeatureEvaluator {
-            synchronized(this) {
-                if (!::taskbarFeatureEvaluator.isInitialized) {
-                    taskbarFeatureEvaluator = TaskbarFeatureEvaluator(taskbarActivityContext)
-                }
-                return taskbarFeatureEvaluator
-            }
-        }
-    }
-
     val hasAllApps = true
     val hasAppIcons = true
     val hasBubbles = false
@@ -59,4 +42,24 @@
 
     val isLandscape: Boolean
         get() = taskbarActivityContext.deviceProfile.isLandscape
+
+    fun onDestroy() {
+        taskbarFeatureEvaluator = null
+    }
+
+    companion object {
+        @Volatile private var taskbarFeatureEvaluator: TaskbarFeatureEvaluator? = null
+
+        @JvmStatic
+        fun getInstance(
+            taskbarActivityContext: TaskbarActivityContext,
+        ): TaskbarFeatureEvaluator {
+            synchronized(this) {
+                if (taskbarFeatureEvaluator == null) {
+                    taskbarFeatureEvaluator = TaskbarFeatureEvaluator(taskbarActivityContext)
+                }
+                return taskbarFeatureEvaluator!!
+            }
+        }
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarIconSpecs.kt b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarIconSpecs.kt
index 6be0828..e55cb1f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarIconSpecs.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarIconSpecs.kt
@@ -37,6 +37,8 @@
 
     val minimumTaskbarIconTouchSize = TaskbarIconSize(48)
 
+    val transientOrPinnedTaskbarIconPaddingSize = iconSize52dp
+
     val transientTaskbarIconSizeByGridSize =
         mapOf(
             TransientTaskbarIconSizeKey(6, 5, false) to iconSize52dp,
diff --git a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarSpecsEvaluator.kt b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarSpecsEvaluator.kt
index f37b2c1..822ca64 100644
--- a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarSpecsEvaluator.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarSpecsEvaluator.kt
@@ -31,7 +31,10 @@
     private var taskbarContainer: List<TaskbarContainer> = emptyList()
 
     val taskbarIconPadding: Int =
-        if (TaskbarIconSpecs.iconSize52dp.size > taskbarIconSize.size) {
+        if (
+            TaskbarIconSpecs.transientOrPinnedTaskbarIconPaddingSize.size > taskbarIconSize.size &&
+                !taskbarFeatureEvaluator.hasNavButtons
+        ) {
             (TaskbarIconSpecs.iconSize52dp.size - taskbarIconSize.size) / 2
         } else {
             0
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java
index 039c0a0..93cbdc7 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java
@@ -22,7 +22,6 @@
 import android.app.ActivityTaskManager;
 import android.app.PendingIntent;
 import android.content.Intent;
-import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
 import android.util.Pair;
@@ -66,13 +65,7 @@
         }
         Pair<Intent, ActivityOptions> options = remoteResponse.getLaunchOptions(view);
         ActivityOptionsWrapper activityOptions = mLauncher.getAppTransitionManager()
-                .getActivityLaunchOptions(hostView);
-        Object itemInfo = hostView.getTag();
-        IBinder launchCookie = null;
-        if (itemInfo instanceof ItemInfo) {
-            launchCookie = mLauncher.getLaunchCookie((ItemInfo) itemInfo);
-            activityOptions.options.setLaunchCookie(launchCookie);
-        }
+                .getActivityLaunchOptions(hostView, (ItemInfo) hostView.getTag());
         if (Utilities.ATLEAST_S && !pendingIntent.isActivity()) {
             // In the event this pending intent eventually launches an activity, i.e. a trampoline,
             // use the Quickstep transition animation.
@@ -81,7 +74,7 @@
                         .registerRemoteAnimationForNextActivityStart(
                                 pendingIntent.getCreatorPackage(),
                                 activityOptions.options.getRemoteAnimationAdapter(),
-                                launchCookie);
+                                activityOptions.options.getLaunchCookie());
             } catch (RemoteException e) {
                 // Do nothing.
             }
@@ -92,7 +85,7 @@
                 ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
         options = Pair.create(options.first, activityOptions.options);
         if (pendingIntent.isActivity()) {
-            logAppLaunch(itemInfo);
+            logAppLaunch(hostView.getTag());
         }
         return RemoteViews.startPendingIntent(hostView, pendingIntent, options);
     }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 0b385d9..d6ea653 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -41,7 +41,6 @@
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTION_EXIT_HOME;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTION_EXIT_INTERRUPTED;
-import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
 import static com.android.launcher3.popup.QuickstepSystemShortcut.getSplitSelectShortcutByPosition;
 import static com.android.launcher3.popup.SystemShortcut.APP_INFO;
 import static com.android.launcher3.popup.SystemShortcut.BUBBLE_SHORTCUT;
@@ -64,8 +63,8 @@
 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.QUICK_SWITCH_FROM_HOME_FALLBACK;
 import static com.android.quickstep.util.AnimUtils.completeRunnableListCallback;
 import static com.android.quickstep.util.SplitAnimationTimings.TABLET_HOME_TO_SPLIT;
-import static com.android.wm.shell.shared.desktopmode.DesktopModeFlags.WALLPAPER_ACTIVITY;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY;
+import static com.android.wm.shell.shared.desktopmode.DesktopModeFlags.WALLPAPER_ACTIVITY;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_50_50;
 
 import android.animation.Animator;
@@ -83,7 +82,6 @@
 import android.media.permission.SafeCloseable;
 import android.os.Build;
 import android.os.Bundle;
-import android.os.IBinder;
 import android.os.IRemoteCallback;
 import android.os.SystemProperties;
 import android.os.Trace;
@@ -202,8 +200,6 @@
 import com.android.systemui.unfold.updates.RotationChangeProvider;
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 
-import kotlin.Unit;
-
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -216,6 +212,8 @@
 import java.util.function.Predicate;
 import java.util.stream.Stream;
 
+import kotlin.Unit;
+
 public class QuickstepLauncher extends Launcher implements RecentsViewContainer,
         SystemShortcut.BubbleActivityStarter {
     private static final boolean TRACE_LAYOUTS =
@@ -229,7 +227,6 @@
     private FixedContainerItems mAllAppsPredictions;
     private HotseatPredictionController mHotseatPredictionController;
     private DepthController mDepthController;
-    private @Nullable DesktopVisibilityController mDesktopVisibilityController;
     private QuickstepTransitionManager mAppTransitionManager;
 
     private OverviewActionsView<?> mActionsView;
@@ -303,8 +300,6 @@
         mTISBindHelper = new TISBindHelper(this, this::onTISConnected);
         mDepthController = new DepthController(this);
         if (DesktopModeStatus.canEnterDesktopMode(this)) {
-            mDesktopVisibilityController = new DesktopVisibilityController(this);
-            mDesktopVisibilityController.registerSystemUiListener();
             mSplitSelectStateController.initSplitFromDesktopController(this,
                     overviewComponentObserver);
         }
@@ -556,10 +551,6 @@
             mLauncherUnfoldAnimationController.onDestroy();
         }
 
-        if (mDesktopVisibilityController != null) {
-            mDesktopVisibilityController.unregisterSystemUiListener();
-        }
-
         if (mSplitSelectStateController != null) {
             mSplitSelectStateController.onDestroy();
         }
@@ -1013,10 +1004,11 @@
 
     @Override
     public void setResumed() {
+        DesktopVisibilityController desktopVisibilityController = getDesktopVisibilityController();
         if (!WALLPAPER_ACTIVITY.isEnabled(this)
-                && mDesktopVisibilityController != null
-                && mDesktopVisibilityController.areDesktopTasksVisible()
-                && !mDesktopVisibilityController.isRecentsGestureInProgress()) {
+                && desktopVisibilityController != null
+                && desktopVisibilityController.areDesktopTasksVisible()
+                && !desktopVisibilityController.isRecentsGestureInProgress()) {
             // Return early to skip setting activity to appear as resumed
             // TODO: b/333533253 - Remove after flag rollout
             return;
@@ -1156,8 +1148,9 @@
     }
 
     @Nullable
+    @Override
     public DesktopVisibilityController getDesktopVisibilityController() {
-        return mDesktopVisibilityController;
+        return mTISBindHelper.getDesktopVisibilityController();
     }
 
     @Nullable
@@ -1192,7 +1185,8 @@
 
     @Override
     public ActivityOptionsWrapper getActivityLaunchOptions(View v, @Nullable ItemInfo item) {
-        ActivityOptionsWrapper activityOptions = mAppTransitionManager.getActivityLaunchOptions(v);
+        ActivityOptionsWrapper activityOptions = mAppTransitionManager.getActivityLaunchOptions(
+                v, item != null ? item : (ItemInfo) v.getTag());
         if (mLastTouchUpTime > 0) {
             activityOptions.options.setSourceInfo(ActivityOptions.SourceInfo.TYPE_LAUNCHER,
                     mLastTouchUpTime);
@@ -1232,43 +1226,6 @@
         mSplitWithKeyboardShortcutController.enterStageSplit(leftOrTop);
     }
 
-    /**
-     * Return a new launch cookie for the activity launch if supported.
-     *
-     * @param info the item info for the launch
-     */
-    public IBinder getLaunchCookie(ItemInfo info) {
-        if (info == null) {
-            return null;
-        }
-        switch (info.container) {
-            case Favorites.CONTAINER_DESKTOP:
-            case Favorites.CONTAINER_HOTSEAT:
-            case Favorites.CONTAINER_PRIVATESPACE:
-                // Fall through and continue it's on the workspace (we don't support swiping back
-                // to other containers like all apps or the hotseat predictions (which can change)
-                break;
-            default:
-                if (info.container >= 0) {
-                    // Also allow swiping to folders
-                    break;
-                }
-                // Reset any existing launch cookies associated with the cookie
-                return ObjectWrapper.wrap(NO_MATCHING_ID);
-        }
-        switch (info.itemType) {
-            case Favorites.ITEM_TYPE_APPLICATION:
-            case Favorites.ITEM_TYPE_DEEP_SHORTCUT:
-            case Favorites.ITEM_TYPE_APPWIDGET:
-                // Fall through and continue if it's an app, shortcut, or widget
-                break;
-            default:
-                // Reset any existing launch cookies associated with the cookie
-                return ObjectWrapper.wrap(NO_MATCHING_ID);
-        }
-        return ObjectWrapper.wrap(new Integer(info.id));
-    }
-
     @Override
     public void onDisplayInfoChanged(Context context, DisplayController.Info info, int flags) {
         super.onDisplayInfoChanged(context, info, flags);
@@ -1347,8 +1304,9 @@
 
     @Override
     public boolean areDesktopTasksVisible() {
-        if (mDesktopVisibilityController != null) {
-            return mDesktopVisibilityController.areDesktopTasksVisible();
+        DesktopVisibilityController desktopVisibilityController = getDesktopVisibilityController();
+        if (desktopVisibilityController != null) {
+            return desktopVisibilityController.areDesktopTasksVisible();
         }
         return false;
     }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
index fa80dc2..030a7ac 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
@@ -26,7 +26,6 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.ActivityContext;
 import com.android.quickstep.util.BaseDepthController;
@@ -202,17 +201,6 @@
     }
 
     @Override
-    public float[] getOverviewScaleAndOffset(Launcher launcher) {
-        if (!FeatureFlags.ENABLE_ALL_APPS_FROM_OVERVIEW.get()) {
-            return super.getOverviewScaleAndOffset(launcher);
-        }
-        // This handles the case of returning to the previous app from Overview -> All Apps gesture.
-        // This is the start scale/offset of overview that will be used for that transition.
-        // TODO (b/283336332): Translate in Y direction (ideally with overview resistance).
-        return new float[] {0.5f /* scale */, NO_OFFSET};
-    }
-
-    @Override
     public int getWorkspaceScrimColor(Launcher launcher) {
         return launcher.getDeviceProfile().isTablet
                 ? launcher.getResources().getColor(R.color.widgets_picker_scrim)
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
index d1aa472..ff726e6 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
@@ -42,7 +42,6 @@
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.taskbar.LauncherTaskbarUIController;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
@@ -221,11 +220,6 @@
             mCancelSplitRunnable.accept(animatorSet, duration);
             animatorSet.start();
         }
-        if (FeatureFlags.ENABLE_PREMIUM_HAPTICS_ALL_APPS.get() &&
-                ((mFromState == NORMAL && mToState == ALL_APPS)
-                        || (mFromState == ALL_APPS && mToState == NORMAL)) && isFling) {
-            mVibratorWrapper.vibrateForDragBump();
-        }
     }
 
     private void onMotionPauseDetected() {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
index b5914a1..b6a0ab3 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
@@ -29,7 +29,6 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.allapps.AllAppsTransitionController;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.touch.AbstractStateChangeTouchController;
 import com.android.launcher3.touch.AllAppsSwipeController;
@@ -93,9 +92,7 @@
     @Override
     protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
         if (fromState == ALL_APPS && !isDragTowardPositive) {
-            return FeatureFlags.ENABLE_ALL_APPS_FROM_OVERVIEW.get()
-                    ? mLauncher.getStateManager().getLastState()
-                    : NORMAL;
+            return NORMAL;
         } else if (fromState == NORMAL && shouldOpenAllApps(isDragTowardPositive)) {
             return ALL_APPS;
         }
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 55489bb..240d6ad 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -45,7 +45,6 @@
 import static com.android.launcher3.util.SystemUiController.UI_STATE_FULLSCREEN_TASK;
 import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
 import static com.android.launcher3.util.window.RefreshRateTracker.getSingleFrameMs;
-import static com.android.quickstep.GestureState.GestureEndTarget.ALL_APPS;
 import static com.android.quickstep.GestureState.GestureEndTarget.HOME;
 import static com.android.quickstep.GestureState.GestureEndTarget.LAST_TASK;
 import static com.android.quickstep.GestureState.GestureEndTarget.NEW_TASK;
@@ -193,7 +192,6 @@
 
     // Null if the recents animation hasn't started yet or has been canceled or finished.
     protected @Nullable RecentsAnimationController mRecentsAnimationController;
-    protected @Nullable RecentsAnimationController mDeferredCleanupRecentsAnimationController;
     protected RecentsAnimationTargets mRecentsAnimationTargets;
     protected @Nullable RECENTS_CONTAINER mContainer;
     protected @Nullable RECENTS_VIEW mRecentsView;
@@ -265,8 +263,6 @@
             getNextStateFlag("STATE_CURRENT_TASK_FINISHED");
     private static final int STATE_FINISH_WITH_NO_END =
             getNextStateFlag("STATE_FINISH_WITH_NO_END");
-    private static final int STATE_SETTLED_ON_ALL_APPS =
-            getNextStateFlag("STATE_SETTLED_ON_ALL_APPS");
 
     private static final int LAUNCHER_UI_STATES =
             STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_LAUNCHER_STARTED |
@@ -320,7 +316,6 @@
     private boolean mGestureStarted;
     private boolean mLogDirectionUpOrLeft = true;
     private boolean mIsLikelyToStartNewTask;
-    private boolean mIsInAllAppsRegion;
 
     private final long mTouchTimeMs;
     private long mLauncherFrameDrawnTime;
@@ -457,9 +452,6 @@
                 this::finishCurrentTransitionToHome);
         mStateCallback.runOnceAtState(STATE_SCALED_CONTROLLER_HOME | STATE_CURRENT_TASK_FINISHED,
                 this::reset);
-        mStateCallback.runOnceAtState(STATE_SETTLED_ON_ALL_APPS | STATE_SCREENSHOT_CAPTURED
-                        | STATE_GESTURE_COMPLETED,
-                this::finishCurrentTransitionToAllApps);
 
         mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED
                         | STATE_LAUNCHER_DRAWN | STATE_SCALED_CONTROLLER_RECENTS
@@ -540,14 +532,7 @@
             HashMap<Integer, ThumbnailData> snapshots =
                     mGestureState.consumeRecentsAnimationCanceledSnapshot();
             if (snapshots != null) {
-                mRecentsView.switchToScreenshot(snapshots, () -> {
-                    if (mRecentsAnimationController != null) {
-                        mRecentsAnimationController.cleanupScreenshot();
-                    } else if (mDeferredCleanupRecentsAnimationController != null) {
-                        mDeferredCleanupRecentsAnimationController.cleanupScreenshot();
-                        mDeferredCleanupRecentsAnimationController = null;
-                    }
-                });
+                mRecentsView.switchToScreenshot(snapshots, () -> {});
                 mRecentsView.onRecentsAnimationComplete();
             }
         });
@@ -724,9 +709,7 @@
                 maybeUpdateRecentsAttachedState(true/* animate */, true/* moveRunningTask */);
                 Optional.ofNullable(mContainerInterface.getTaskbarController())
                         .ifPresent(TaskbarUIController::startTranslationSpring);
-                if (!mIsInAllAppsRegion) {
-                    performHapticFeedback();
-                }
+                performHapticFeedback();
             }
 
             @Override
@@ -774,9 +757,7 @@
                 .findTask(mGestureState.getTopRunningTaskId())
                 : null;
         final boolean recentsAttachedToAppWindow;
-        if (mIsInAllAppsRegion) {
-            recentsAttachedToAppWindow = false;
-        } else if (mGestureState.getEndTarget() != null) {
+        if (mGestureState.getEndTarget() != null) {
             recentsAttachedToAppWindow = mGestureState.getEndTarget().recentsAttachedToAppWindow;
         } else if (mContinuingLastGesture
                 && mRecentsView.getRunningTaskIndex() != mRecentsView.getNextPage()) {
@@ -833,31 +814,6 @@
         }
     }
 
-    /**
-     * Update whether user is currently dragging in a region that will trigger all apps.
-     */
-    private void setIsInAllAppsRegion(boolean isInAllAppsRegion) {
-        if (mIsInAllAppsRegion == isInAllAppsRegion
-                || !mContainerInterface.allowAllAppsFromOverview()) {
-            return;
-        }
-        mIsInAllAppsRegion = isInAllAppsRegion;
-
-        // Newly entering or exiting the zone - do haptic and animate recent tasks.
-        VibratorWrapper.INSTANCE.get(mContext).vibrate(OVERVIEW_HAPTIC);
-        maybeUpdateRecentsAttachedState(true);
-
-        if (mContainer != null) {
-            mContainer.getAppsView().getSearchUiManager()
-                    .prepareToFocusEditText(mIsInAllAppsRegion);
-        }
-
-        // Draw active task below Launcher so that All Apps can appear over it.
-        runActionOnRemoteHandles(remoteTargetHandle ->
-                remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(isInAllAppsRegion));
-    }
-
-
     private void buildAnimationController() {
         if (!canCreateNewOrUpdateExistingLauncherTransitionController()) {
             return;
@@ -917,8 +873,6 @@
     @UiThread
     @Override
     public void onCurrentShiftUpdated() {
-        float threshold = DeviceConfigWrapper.get().getAllAppsOverviewThreshold() / 100f;
-        setIsInAllAppsRegion(mCurrentShift.value >= threshold);
         updateSysUiFlags(mCurrentShift.value);
         applyScrollAndTransform();
 
@@ -1025,9 +979,6 @@
                 /* event= */ "cancelRecentsAnimation",
                 /* gestureEvent= */ CANCEL_RECENTS_ANIMATION);
         mActivityInitListener.unregister("AbsSwipeUpHandler.onRecentsAnimationCanceled");
-        // Cache the recents animation controller so we can defer its cleanup to after having
-        // properly cleaned up the screenshot without accidentally using it.
-        mDeferredCleanupRecentsAnimationController = mRecentsAnimationController;
         mStateCallback.setStateOnUiThread(STATE_GESTURE_CANCELLED | STATE_HANDLER_INVALIDATED);
         // Defer clearing the controller and the targets until after we've updated the state
         mRecentsAnimationController = null;
@@ -1203,9 +1154,6 @@
         }
 
         switch (endTarget) {
-            case ALL_APPS:
-                mStateCallback.setState(STATE_SETTLED_ON_ALL_APPS | STATE_CAPTURE_SCREENSHOT);
-                break;
             case HOME:
                 mStateCallback.setState(STATE_SCALED_CONTROLLER_HOME | STATE_CAPTURE_SCREENSHOT);
                 // Notify the SysUI to use fade-in animation when entering PiP
@@ -1324,9 +1272,6 @@
         final boolean willGoToNewTask =
                 isScrollingToNewTask() && Math.abs(velocity.x) > Math.abs(endVelocity);
         final boolean isSwipeUp = endVelocity < 0;
-        if (mIsInAllAppsRegion) {
-            return isSwipeUp ? ALL_APPS : LAST_TASK;
-        }
         if (!isSwipeUp) {
             final boolean isCenteredOnNewTask = mRecentsView != null
                     && mRecentsView.getDestinationPage() != mRecentsView.getRunningTaskIndex();
@@ -1342,9 +1287,7 @@
         // Fully gestural mode.
         final boolean isFlingX = Math.abs(velocity.x) > mContext.getResources()
                 .getDimension(R.dimen.quickstep_fling_threshold_speed);
-        if (mIsInAllAppsRegion) {
-            return ALL_APPS;
-        } else if (isScrollingToNewTask && isFlingX) {
+        if (isScrollingToNewTask && isFlingX) {
             // Flinging towards new task takes precedence over mIsMotionPaused (which only
             // checks y-velocity).
             return NEW_TASK;
@@ -1399,8 +1342,7 @@
                     .setUserIsNotGoingHome(endTarget != GestureState.GestureEndTarget.HOME);
         }
 
-        float endShift = endTarget == ALL_APPS ? mDragLengthFactor
-                : endTarget.isLauncher ? 1 : 0;
+        float endShift = endTarget.isLauncher ? 1 : 0;
         final float startShift;
         if (!isFling) {
             long expectedDuration = Math.abs(Math.round((endShift - currentShift)
@@ -2027,12 +1969,6 @@
         reset();
     }
 
-    @UiThread
-    private void finishCurrentTransitionToAllApps() {
-        finishCurrentTransitionToHome();
-        reset();
-    }
-
     private void reset() {
         mStateCallback.setStateOnUiThread(STATE_HANDLER_INVALIDATED);
         if (mContainer != null) {
@@ -2174,7 +2110,6 @@
     private void updateThumbnail() {
         if (mGestureState.getEndTarget() == HOME
                 || mGestureState.getEndTarget() == NEW_TASK
-                || mGestureState.getEndTarget() == ALL_APPS
                 || mRecentsView == null) {
             // Capture the screenshot before finishing the transition to home or quickswitching to
             // ensure it's taken in the correct orientation, but no need to update the thumbnail.
diff --git a/quickstep/src/com/android/quickstep/BaseContainerInterface.java b/quickstep/src/com/android/quickstep/BaseContainerInterface.java
index 777761b..bf3a662 100644
--- a/quickstep/src/com/android/quickstep/BaseContainerInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseContainerInterface.java
@@ -74,9 +74,6 @@
     public abstract boolean deferStartingActivity(RecentsAnimationDeviceState deviceState,
             MotionEvent ev);
 
-    /** @return whether to allow going to All Apps from Overview. */
-    public abstract boolean allowAllAppsFromOverview();
-
     /**
      * Returns the color of the scrim behind overview when at rest in this state.
      * Return {@link Color#TRANSPARENT} for no scrim.
@@ -131,7 +128,9 @@
 
     @Nullable
     public DesktopVisibilityController getDesktopVisibilityController() {
-        return null;
+        CONTAINER_TYPE container = getCreatedContainer();
+
+        return container == null ? null : container.getDesktopVisibilityController();
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/DeviceConfigWrapper.kt b/quickstep/src/com/android/quickstep/DeviceConfigWrapper.kt
index e6822ff..f610014 100644
--- a/quickstep/src/com/android/quickstep/DeviceConfigWrapper.kt
+++ b/quickstep/src/com/android/quickstep/DeviceConfigWrapper.kt
@@ -142,13 +142,6 @@
             "Controls extra dp on the nav bar sides to trigger LPNH. Can be negative for a smaller touch region."
         )
 
-    val allAppsOverviewThreshold =
-        propReader.get(
-            "ALL_APPS_OVERVIEW_THRESHOLD",
-            180,
-            "Threshold to open All Apps from Overview"
-        )
-
     /** Dump config values. */
     fun dump(prefix: String, writer: PrintWriter) {
         writer.println("$prefix DeviceConfigWrapper:")
@@ -169,7 +162,6 @@
         writer.println("$prefix\tenableLpnhDeepPress=$enableLpnhDeepPress")
         writer.println("$prefix\tlpnhHapticHintDelay=$lpnhHapticHintDelay")
         writer.println("$prefix\tlpnhExtraTouchWidthDp=$lpnhExtraTouchWidthDp")
-        writer.println("$prefix\tallAppsOverviewThreshold=$allAppsOverviewThreshold")
     }
 
     companion object {
diff --git a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
index 94a4527..df83eb2 100644
--- a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
@@ -133,11 +133,6 @@
     }
 
     @Override
-    public boolean allowAllAppsFromOverview() {
-        return false;
-    }
-
-    @Override
     public boolean deferStartingActivity(RecentsAnimationDeviceState deviceState, MotionEvent ev) {
         // In non-gesture mode, user might be clicking on the home button which would directly
         // start the home activity instead of going through recents. In that case, defer starting
diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java
index 81c9d4a..9cc463a 100644
--- a/quickstep/src/com/android/quickstep/GestureState.java
+++ b/quickstep/src/com/android/quickstep/GestureState.java
@@ -179,7 +179,6 @@
     private RemoteAnimationTarget[] mLastAppearedTaskTargets;
     private Set<Integer> mPreviouslyAppearedTaskIds = new HashSet<>();
     private int[] mLastStartedTaskId = new int[]{INVALID_TASK_ID, INVALID_TASK_ID};
-    private RecentsAnimationController mRecentsAnimationController;
     private HashMap<Integer, ThumbnailData> mRecentsAnimationCanceledSnapshots;
 
     /** The time when the swipe up gesture is triggered. */
@@ -470,7 +469,6 @@
     @Override
     public void onRecentsAnimationStart(RecentsAnimationController controller,
             RecentsAnimationTargets targets) {
-        mRecentsAnimationController = controller;
         mStateCallback.setState(STATE_RECENTS_ANIMATION_STARTED);
     }
 
@@ -480,10 +478,6 @@
         mStateCallback.setState(STATE_RECENTS_ANIMATION_CANCELED);
         mStateCallback.setState(STATE_RECENTS_ANIMATION_ENDED);
         if (mRecentsAnimationCanceledSnapshots != null) {
-            // Clean up the screenshot to finalize the recents animation cancel
-            if (mRecentsAnimationController != null) {
-                mRecentsAnimationController.cleanupScreenshot();
-            }
             mRecentsAnimationCanceledSnapshots = null;
         }
     }
@@ -522,7 +516,7 @@
     HashMap<Integer, ThumbnailData> consumeRecentsAnimationCanceledSnapshot() {
         if (mRecentsAnimationCanceledSnapshots != null) {
             HashMap<Integer, ThumbnailData> data =
-                    new HashMap<Integer, ThumbnailData>(mRecentsAnimationCanceledSnapshots);
+                    new HashMap<>(mRecentsAnimationCanceledSnapshots);
             mRecentsAnimationCanceledSnapshots = null;
             return data;
         }
diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index e9fe2f7..5308436 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -18,7 +18,6 @@
 import static com.android.app.animation.Interpolators.LINEAR;
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
-import static com.android.launcher3.LauncherState.FLOATING_SEARCH_BAR;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
@@ -40,9 +39,7 @@
 import com.android.launcher3.LauncherInitListener;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.anim.PendingAnimation;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.statehandlers.DepthController;
-import com.android.launcher3.statehandlers.DesktopVisibilityController;
 import com.android.launcher3.statemanager.StateManager;
 import com.android.launcher3.taskbar.LauncherTaskbarUIController;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
@@ -169,16 +166,6 @@
 
     @Nullable
     @Override
-    public DesktopVisibilityController getDesktopVisibilityController() {
-        QuickstepLauncher launcher = getCreatedContainer();
-        if (launcher == null) {
-            return null;
-        }
-        return launcher.getDesktopVisibilityController();
-    }
-
-    @Nullable
-    @Override
     public LauncherTaskbarUIController getTaskbarController() {
         QuickstepLauncher launcher = getCreatedContainer();
         if (launcher == null) {
@@ -271,13 +258,6 @@
     }
 
     @Override
-    public boolean allowAllAppsFromOverview() {
-        return FeatureFlags.ENABLE_ALL_APPS_FROM_OVERVIEW.get()
-                // If floating search bar would not show in overview, don't allow all apps gesture.
-                && OVERVIEW.areElementsVisible(getCreatedContainer(), FLOATING_SEARCH_BAR);
-    }
-
-    @Override
     public boolean isInLiveTileMode() {
         QuickstepLauncher launcher = getCreatedContainer();
 
diff --git a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
index a4ee3dd..360c216 100644
--- a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
+++ b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
@@ -452,7 +452,6 @@
                 mQuickstepTransitionManager.createWallpaperOpenAnimations(
                     new RemoteAnimationTarget[]{mBackTarget},
                     new RemoteAnimationTarget[0],
-                    false /* fromUnlock */,
                     resolveRectF,
                     cornerRadius,
                     mBackInProgress /* fromPredictiveBack */);
diff --git a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
index f653e60..d2dcd7b 100644
--- a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
+++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
@@ -20,7 +20,6 @@
 import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.Utilities.mapBoundToRange;
-import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
 import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
 import static com.android.launcher3.views.FloatingIconView.getFloatingIconView;
 
@@ -42,7 +41,7 @@
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
-import com.android.launcher3.util.ObjectWrapper;
+import com.android.launcher3.util.StableViewInfo;
 import com.android.launcher3.views.ClipIconView;
 import com.android.launcher3.views.FloatingIconView;
 import com.android.launcher3.views.FloatingView;
@@ -301,18 +300,7 @@
             return null;
         }
 
-        // Find the associated item info for the launch cookie (if available), note that predicted
-        // apps actually have an id of -1, so use another default id here
-        int launchCookieItemId = NO_MATCHING_ID;
-        for (IBinder cookie : launchCookies) {
-            Integer itemId = ObjectWrapper.unwrap(cookie);
-            if (itemId != null) {
-                launchCookieItemId = itemId;
-                break;
-            }
-        }
-
-        return mContainer.getFirstMatchForAppClose(launchCookieItemId,
+        return mContainer.getFirstMatchForAppClose(StableViewInfo.fromLaunchCookies(launchCookies),
                 sourceTaskView.getFirstTask().key.getComponent().getPackageName(),
                 UserHandle.of(sourceTaskView.getFirstTask().key.userId),
                 false /* supportsAllAppsState */);
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index 6d5cb4b..9c60693 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -63,6 +63,7 @@
 import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.desktop.DesktopRecentsTransitionController;
 import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.statehandlers.DesktopVisibilityController;
 import com.android.launcher3.statemanager.StateManager;
 import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory;
 import com.android.launcher3.statemanager.StateManager.StateHandler;
@@ -525,4 +526,10 @@
     public boolean isRecentsViewVisible() {
         return getStateManager().getState().isRecentsViewVisible();
     }
+
+    @Nullable
+    @Override
+    public DesktopVisibilityController getDesktopVisibilityController() {
+        return mTISBindHelper.getDesktopVisibilityController();
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
index 7b9b560..0c5806b 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
@@ -174,19 +174,7 @@
         });
     }
 
-    @BinderThread
-    @Override
-    public boolean onSwitchToScreenshot(Runnable onFinished) {
-        Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
-            for (RecentsAnimationListener listener : getListeners()) {
-                if (listener.onSwitchToScreenshot(onFinished)) return;
-            }
-            onFinished.run();
-        });
-        return true;
-    }
-
-    private final void onAnimationFinished(RecentsAnimationController controller) {
+    private void onAnimationFinished(RecentsAnimationController controller) {
         Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
             ActiveGestureLog.INSTANCE.addLog(
                     /* event= */ "RecentsAnimationCallbacks.onAnimationFinished",
@@ -242,12 +230,5 @@
          * Callback made when a task started from the recents is ready for an app transition.
          */
         default void onTasksAppeared(@NonNull RemoteAnimationTarget[] appearedTaskTarget) {}
-
-        /**
-         * @return whether this will call onFinished or not (onFinished should only be called once).
-         */
-        default boolean onSwitchToScreenshot(Runnable onFinished) {
-            return false;
-        }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
index adcf4ef..190d526 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
@@ -19,11 +19,9 @@
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.FINISH_RECENTS_ANIMATION;
 
-import android.content.Context;
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.util.Log;
-import android.view.IRecentsAnimationController;
 import android.view.SurfaceControl;
 import android.view.WindowManagerGlobal;
 import android.window.PictureInPictureSurfaceTransaction;
@@ -34,11 +32,11 @@
 import com.android.internal.os.IResultReceiver;
 import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.RunnableList;
-import com.android.quickstep.util.ActiveGestureErrorDetector;
 import com.android.quickstep.util.ActiveGestureLog;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
 import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
+import com.android.wm.shell.recents.IRecentsAnimationController;
 
 import java.io.PrintWriter;
 import java.util.function.Consumer;
@@ -90,15 +88,6 @@
         }
     }
 
-    /**
-     * Remove task remote animation target from
-     * {@link RecentsAnimationCallbacks#onTasksAppeared}}.
-     */
-    @UiThread
-    public void removeTaskTarget(int targetTaskId) {
-        UI_HELPER_EXECUTOR.execute(() -> mController.removeTask(targetTaskId));
-    }
-
     @UiThread
     public void finishAnimationToHome() {
         finishController(true /* toRecents */, null, false /* sendUserLeaveHint */);
@@ -173,19 +162,6 @@
     }
 
     /**
-     * @see IRecentsAnimationController#cleanupScreenshot()
-     */
-    @UiThread
-    public void cleanupScreenshot() {
-        UI_HELPER_EXECUTOR.execute(() -> {
-            ActiveGestureLog.INSTANCE.addLog(
-                    "cleanupScreenshot",
-                    ActiveGestureErrorDetector.GestureEvent.CLEANUP_SCREENSHOT);
-            mController.cleanupScreenshot();
-        });
-    }
-
-    /**
      * @see RecentsAnimationControllerCompat#detachNavigationBarFromApp
      */
     @UiThread
@@ -194,14 +170,6 @@
     }
 
     /**
-     * @see IRecentsAnimationController#animateNavigationBarToApp(long)
-     */
-    @UiThread
-    public void animateNavigationBarToApp(long duration) {
-        UI_HELPER_EXECUTOR.execute(() -> mController.animateNavigationBarToApp(duration));
-    }
-
-    /**
      * @see IRecentsAnimationController#setWillFinishToHome(boolean)
      */
     @UiThread
diff --git a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
index 1be60de..8adc11a 100644
--- a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
+++ b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
@@ -68,7 +68,7 @@
      */
     public RemoteTargetGluer(Context context, BaseContainerInterface sizingStrategy) {
         DesktopVisibilityController desktopVisibilityController =
-                LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
+                sizingStrategy.getDesktopVisibilityController();
         if (desktopVisibilityController != null) {
             int visibleTasksCount = desktopVisibilityController.getVisibleDesktopTasksCount();
             if (visibleTasksCount > 0) {
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index dde16c8..b4bd3e3 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -43,8 +43,6 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.util.Log;
-import android.view.IRecentsAnimationController;
-import android.view.IRecentsAnimationRunner;
 import android.view.IRemoteAnimationRunner;
 import android.view.MotionEvent;
 import android.view.RemoteAnimationTarget;
@@ -87,6 +85,8 @@
 import com.android.wm.shell.desktopmode.IDesktopTaskListener;
 import com.android.wm.shell.draganddrop.IDragAndDrop;
 import com.android.wm.shell.onehanded.IOneHanded;
+import com.android.wm.shell.recents.IRecentsAnimationController;
+import com.android.wm.shell.recents.IRecentsAnimationRunner;
 import com.android.wm.shell.recents.IRecentTasks;
 import com.android.wm.shell.recents.IRecentTasksListener;
 import com.android.wm.shell.shared.GroupedRecentTaskInfo;
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index 49ec597..289a2c1 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -289,38 +289,10 @@
                             true /*shown*/, null /* animatorHandler */);
                 }
                 if (mController != null) {
-                    if (mLastAppearedTaskTargets != null) {
-                        for (RemoteAnimationTarget lastTarget : mLastAppearedTaskTargets) {
-                            for (RemoteAnimationTarget appearedTarget : appearedTaskTargets) {
-                                if (lastTarget != null &&
-                                        appearedTarget.taskId != lastTarget.taskId) {
-                                    mController.removeTaskTarget(lastTarget.taskId);
-                                }
-                            }
-                        }
-                    }
                     mLastAppearedTaskTargets = appearedTaskTargets;
                     mLastGestureState.updateLastAppearedTaskTargets(mLastAppearedTaskTargets);
                 }
             }
-
-            @Override
-            public boolean onSwitchToScreenshot(Runnable onFinished) {
-                if (!containerInterface.isInLiveTileMode()
-                        || containerInterface.getCreatedContainer() == null) {
-                    // No need to switch since tile is already a screenshot.
-                    onFinished.run();
-                } else {
-                    final RecentsView recentsView =
-                            containerInterface.getCreatedContainer().getOverviewPanel();
-                    if (recentsView != null) {
-                        recentsView.switchToScreenshot(onFinished);
-                    } else {
-                        onFinished.run();
-                    }
-                }
-                return true;
-            }
         });
         final long eventTime = gestureState.getSwipeUpStartTimeMs();
         mCallbacks.addListener(gestureState);
@@ -329,10 +301,7 @@
         final ActivityOptions options = ActivityOptions.makeBasic();
         options.setPendingIntentBackgroundActivityStartMode(
                 ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS);
-        // Use regular (non-transient) launch for all apps page to control IME.
-        if (!containerInterface.allowAllAppsFromOverview()) {
-            options.setTransientLaunch();
-        }
+        options.setTransientLaunch();
         options.setSourceInfo(ActivityOptions.SourceInfo.TYPE_RECENTS_ANIMATION, eventTime);
 
         // Notify taskbar that we should skip reacting to launcher visibility change to
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index 1a09691..d8063ba 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -304,14 +304,6 @@
                         }
                     }
                 });
-            } else {
-                // There is no transition animation for app launch from recent in live tile mode so
-                // we have to trigger the navigation bar animation from system here.
-                final RecentsAnimationController controller =
-                        recentsView.getRecentsAnimationController();
-                if (controller != null) {
-                    controller.animateNavigationBarToApp(RECENTS_LAUNCH_DURATION);
-                }
             }
             topMostSimulators = remoteTargetHandles;
         }
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 4587bdd..ce66b04 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -94,6 +94,7 @@
 import com.android.launcher3.anim.AnimatedFloat;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.provider.RestoreDbTask;
+import com.android.launcher3.statehandlers.DesktopVisibilityController;
 import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.taskbar.TaskbarActivityContext;
 import com.android.launcher3.taskbar.TaskbarManager;
@@ -379,6 +380,15 @@
             ));
         }
 
+        @BinderThread
+        @Override
+        public void appTransitionPending(boolean pending) {
+            MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(tis ->
+                    executeForTaskbarManager(
+                            taskbarManager -> taskbarManager.appTransitionPending(pending))
+            ));
+        }
+
         /**
          * Preloads the Overview activity.
          * <p>
@@ -453,6 +463,18 @@
             return tis.mTaskbarManager;
         }
 
+        /**
+         * Returns the {@link DesktopVisibilityController}
+         * <p>
+         * Returns {@code null} if TouchInteractionService is not connected
+         */
+        @Nullable
+        public DesktopVisibilityController getDesktopVisibilityController() {
+            TouchInteractionService tis = mTis.get();
+            if (tis == null) return null;
+            return tis.mDesktopVisibilityController;
+        }
+
         @VisibleForTesting
         public void injectFakeTrackpadForTesting() {
             TouchInteractionService tis = mTis.get();
@@ -628,6 +650,8 @@
 
     private NavigationMode mGestureStartNavMode = null;
 
+    private DesktopVisibilityController mDesktopVisibilityController;
+
     @Override
     public void onCreate() {
         super.onCreate();
@@ -648,7 +672,9 @@
                 mInputDeviceListener.onInputDeviceAdded(inputDeviceId);
             }
         }
-        mTaskbarManager = new TaskbarManager(this, mAllAppsActionManager, mNavCallbacks);
+        mDesktopVisibilityController = new DesktopVisibilityController(this);
+        mTaskbarManager = new TaskbarManager(
+                this, mAllAppsActionManager, mNavCallbacks, mDesktopVisibilityController);
         mInputConsumer = InputConsumerController.getRecentsAnimationInputConsumer();
 
         // Call runOnUserUnlocked() before any other callbacks to ensure everything is initialized.
@@ -743,8 +769,8 @@
     private void onOverviewTargetChange(boolean isHomeAndOverviewSame) {
         mAllAppsActionManager.setHomeAndOverviewSame(isHomeAndOverviewSame);
 
-        StatefulActivity newOverviewActivity = mOverviewComponentObserver.getActivityInterface()
-                .getCreatedContainer();
+        StatefulActivity<?> newOverviewActivity =
+                mOverviewComponentObserver.getActivityInterface().getCreatedContainer();
         if (newOverviewActivity != null) {
             mTaskbarManager.setActivity(newOverviewActivity);
         }
@@ -808,6 +834,7 @@
         mTrackpadsConnected.clear();
 
         mTaskbarManager.destroy();
+        mDesktopVisibilityController.onDestroy();
         sConnected = false;
 
         ScreenOnTracker.INSTANCE.get(this).removeListener(mScreenOnListener);
@@ -1654,6 +1681,7 @@
             createdOverviewActivity.getDeviceProfile().dump(this, "", pw);
         }
         mTaskbarManager.dumpLogs("", pw);
+        mDesktopVisibilityController.dumpLogs("", pw);
         pw.println("AssistStateManager:");
         AssistStateManager.INSTANCE.get(this).dump("\t", pw);
         SystemUiProxy.INSTANCE.get(this).dump(pw);
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index f4a2738..e67a9bc 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -253,6 +253,9 @@
         }
 
         setFreezeViewVisibility(true);
+        if (mContainer.getDesktopVisibilityController() != null) {
+            mContainer.getDesktopVisibilityController().onLauncherStateChanged(toState);
+        }
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java b/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
index c7c04ed..b583a4b 100644
--- a/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
+++ b/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
@@ -21,7 +21,6 @@
 import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
 import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
 
-import android.animation.AnimatorSet;
 import android.animation.TimeInterpolator;
 import android.content.Context;
 import android.graphics.Matrix;
@@ -34,18 +33,11 @@
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherState;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.PendingAnimation;
-import com.android.launcher3.statemanager.StateManager;
-import com.android.launcher3.statemanager.StatefulActivity;
-import com.android.launcher3.states.StateAnimationConfig;
-import com.android.launcher3.touch.AllAppsSwipeController;
-import com.android.quickstep.DeviceConfigWrapper;
 import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
 import com.android.quickstep.views.RecentsView;
-import com.android.quickstep.views.RecentsViewContainer;
 
 /**
  * Controls an animation that can go beyond progress = 1, at which point resistance should be
@@ -57,10 +49,8 @@
 
     private enum RecentsResistanceParams {
         FROM_APP(0.75f, 0.5f, 1f, false),
-        FROM_APP_TO_ALL_APPS(1f, 0.6f, 0.8f, false),
         FROM_APP_TABLET(1f, 0.7f, 1f, true),
         FROM_APP_TABLET_GRID_ONLY(1f, 1f, 1f, true),
-        FROM_APP_TO_ALL_APPS_TABLET(1f, 0.5f, 0.5f, false),
         FROM_OVERVIEW(1f, 0.75f, 0.5f, false);
 
         RecentsResistanceParams(float scaleStartResist, float scaleMaxResist,
@@ -157,46 +147,10 @@
         RecentsParams params = new RecentsParams(context, recentsOrientedState, dp, scaleTarget,
                 scaleProperty, translationTarget, translationProperty);
         PendingAnimation resistAnim = createRecentsResistanceAnim(params);
-
-        // Apply All Apps animation during the resistance animation.
-        if (recentsOrientedState.getContainerInterface().allowAllAppsFromOverview()) {
-            RecentsViewContainer container =
-                    recentsOrientedState.getContainerInterface().getCreatedContainer();
-            if (container != null) {
-                RecentsView recentsView = container.getOverviewPanel();
-                StateManager<LauncherState, StatefulActivity<LauncherState>> stateManager =
-                        recentsView.getStateManager();
-                if (stateManager.isInStableState(LauncherState.BACKGROUND_APP)
-                        && stateManager.isInTransition()) {
-
-                    // Calculate the resistance progress threshold where All Apps will trigger.
-                    float threshold = getAllAppsThreshold(context, recentsOrientedState, dp);
-
-                    StateAnimationConfig config = new StateAnimationConfig();
-                    AllAppsSwipeController.applyOverviewToAllAppsAnimConfig(dp, config, threshold);
-                    AnimatorSet allAppsAnimator = stateManager.createAnimationToNewWorkspace(
-                            LauncherState.ALL_APPS, config).getTarget();
-                    resistAnim.add(allAppsAnimator);
-                }
-            }
-        }
-
         AnimatorPlaybackController resistanceController = resistAnim.createPlaybackController();
         return new AnimatorControllerWithResistance(normalController, resistanceController);
     }
 
-    private static float getAllAppsThreshold(Context context,
-            RecentsOrientedState recentsOrientedState, DeviceProfile dp) {
-        int transitionDragLength =
-                recentsOrientedState.getContainerInterface().getSwipeUpDestinationAndLength(
-                        dp, context, TEMP_RECT,
-                        recentsOrientedState.getOrientationHandler());
-        float dragLengthFactor = (float) dp.heightPx / transitionDragLength;
-        // -1s are because 0-1 is reserved for the normal transition.
-        float threshold = DeviceConfigWrapper.get().getAllAppsOverviewThreshold() / 100f;
-        return (threshold - 1) / (dragLengthFactor - 1);
-    }
-
     /**
      * Creates the resistance animation for {@link #createForRecents}, or can be used separately
      * when starting from recents, i.e. {@link #createRecentsResistanceFromOverviewAnim}.
@@ -305,17 +259,11 @@
             this.translationTarget = translationTarget;
             this.translationProperty = translationProperty;
             if (dp.isTablet) {
-                resistanceParams =
-                        recentsOrientedState.getContainerInterface().allowAllAppsFromOverview()
-                                ? RecentsResistanceParams.FROM_APP_TO_ALL_APPS_TABLET
-                                : enableGridOnlyOverview()
-                                        ? RecentsResistanceParams.FROM_APP_TABLET_GRID_ONLY
-                                        : RecentsResistanceParams.FROM_APP_TABLET;
+                resistanceParams = enableGridOnlyOverview()
+                        ? RecentsResistanceParams.FROM_APP_TABLET_GRID_ONLY
+                        : RecentsResistanceParams.FROM_APP_TABLET;
             } else {
-                resistanceParams =
-                        recentsOrientedState.getContainerInterface().allowAllAppsFromOverview()
-                                ? RecentsResistanceParams.FROM_APP_TO_ALL_APPS
-                                : RecentsResistanceParams.FROM_APP;
+                resistanceParams = RecentsResistanceParams.FROM_APP;
             }
         }
 
diff --git a/quickstep/src/com/android/quickstep/util/RecentsViewUtils.kt b/quickstep/src/com/android/quickstep/util/RecentsViewUtils.kt
index cf08391..be1af64 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsViewUtils.kt
+++ b/quickstep/src/com/android/quickstep/util/RecentsViewUtils.kt
@@ -28,9 +28,17 @@
  * and extracted functions from RecentsView to facilitate the implementation of unit tests.
  */
 class RecentsViewUtils {
+    /** Takes a screenshot of all [taskView] and return map of taskId to the screenshot */
+    fun screenshotTasks(
+        taskView: TaskView,
+        recentsAnimationController: RecentsAnimationController,
+    ): Map<Int, ThumbnailData> =
+        taskView.taskContainers.associate {
+            it.task.key.id to recentsAnimationController.screenshotTask(it.task.key.id)
+        }
 
     /**
-     * Sort task groups to move desktop tasks to the end of the list.
+     * Sorts task groups to move desktop tasks to the end of the list.
      *
      * @param tasks List of group tasks to be sorted.
      * @return Sorted list of GroupTasks to be used in the RecentsView.
@@ -40,6 +48,7 @@
         return otherTasks + desktopTasks
     }
 
+    /** Returns the expected index of the focus task. */
     fun getFocusedTaskIndex(taskGroups: List<GroupTask>): Int {
         // The focused task index is placed after the desktop tasks views.
         return if (enableLargeDesktopWindowingTile()) {
@@ -49,33 +58,77 @@
         }
     }
 
-    /**
-     * Counts [numChildren] that are [DesktopTaskView] instances.
-     *
-     * @param numChildren Quantity of children to transverse
-     * @param getTaskViewAt Function that provides a TaskView given an index
-     */
-    fun getDesktopTaskViewCount(numChildren: Int, getTaskViewAt: (Int) -> TaskView?): Int =
-        (0 until numChildren).count { getTaskViewAt(it) is DesktopTaskView }
+    /** Counts [TaskView]s that are [DesktopTaskView] instances. */
+    fun getDesktopTaskViewCount(taskViews: Iterable<TaskView>): Int =
+        taskViews.count { it is DesktopTaskView }
+
+    /** Returns a list of all large TaskView Ids from [TaskView]s */
+    fun getLargeTaskViewIds(taskViews: Iterable<TaskView>): List<Int> =
+        taskViews.filter { it.isLargeTile }.map { it.taskViewId }
 
     /**
      * Returns the first TaskView that should be displayed as a large tile.
      *
-     * @param numChildren Quantity of children to transverse
-     * @param getTaskViewAt Function that provides a TaskView given an index
+     * @param taskViews List of [TaskView]s
      */
-    fun getFirstLargeTaskView(numChildren: Int, getTaskViewAt: (Int) -> TaskView?): TaskView? {
-        return (0 until numChildren).firstNotNullOfOrNull { index ->
-            val taskView = getTaskViewAt(index)
-            if (taskView?.isLargeTile == true) taskView else null
+    fun getFirstLargeTaskView(taskViews: Iterable<TaskView>): TaskView? =
+        taskViews.firstOrNull { it.isLargeTile }
+
+    /** Returns the last TaskView that should be displayed as a large tile. */
+    fun getLastLargeTaskView(taskViews: Iterable<TaskView>): TaskView? =
+        taskViews.lastOrNull { it.isLargeTile }
+
+    /** Returns the first [TaskView], with some tasks possibly hidden in the carousel. */
+    fun getFirstTaskViewInCarousel(
+        nonRunningTaskCategoryHidden: Boolean,
+        taskViews: Iterable<TaskView>,
+        runningTaskView: TaskView?,
+    ): TaskView? =
+        taskViews.firstOrNull {
+            it.isVisibleInCarousel(runningTaskView, nonRunningTaskCategoryHidden)
+        }
+
+    /** Returns the last [TaskView], with some tasks possibly hidden in the carousel. */
+    fun getLastTaskViewInCarousel(
+        nonRunningTaskCategoryHidden: Boolean,
+        taskViews: Iterable<TaskView>,
+        runningTaskView: TaskView?,
+    ): TaskView? =
+        taskViews.lastOrNull {
+            it.isVisibleInCarousel(runningTaskView, nonRunningTaskCategoryHidden)
+        }
+
+    /** Returns the current list of [TaskView] children. */
+    fun getTaskViews(taskViewCount: Int, requireTaskViewAt: (Int) -> TaskView): Iterable<TaskView> =
+        (0 until taskViewCount).map(requireTaskViewAt)
+
+    /** Apply attachAlpha to all [TaskView] accordingly to different conditions. */
+    fun applyAttachAlpha(
+        taskViews: Iterable<TaskView>,
+        runningTaskView: TaskView?,
+        runningTaskTileHidden: Boolean,
+        nonRunningTaskCategoryHidden: Boolean,
+    ) {
+        taskViews.forEach { taskView ->
+            val isVisible =
+                if (taskView == runningTaskView) !runningTaskTileHidden
+                else taskView.isVisibleInCarousel(runningTaskView, nonRunningTaskCategoryHidden)
+            taskView.attachAlpha = if (isVisible) 1f else 0f
         }
     }
 
-    fun screenshotTasks(
-        taskView: TaskView,
-        recentsAnimationController: RecentsAnimationController
-    ): Map<Int, ThumbnailData> =
-        taskView.taskContainers.associate {
-            it.task.key.id to recentsAnimationController.screenshotTask(it.task.key.id)
-        }
+    private fun TaskView.isVisibleInCarousel(
+        runningTaskView: TaskView?,
+        nonRunningTaskCategoryHidden: Boolean,
+    ): Boolean =
+        if (!nonRunningTaskCategoryHidden) true
+        else if (runningTaskView == null) true else getCategory() == runningTaskView.getCategory()
+
+    private fun TaskView.getCategory(): TaskViewCategory =
+        if (this is DesktopTaskView) TaskViewCategory.DESKTOP else TaskViewCategory.FULL_SCREEN
+
+    private enum class TaskViewCategory {
+        FULL_SCREEN,
+        DESKTOP,
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java b/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java
index 304b8f4..c3270dc 100644
--- a/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java
+++ b/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java
@@ -30,7 +30,6 @@
 import com.android.launcher3.util.WindowBounds;
 import com.android.launcher3.util.window.CachedDisplayInfo;
 import com.android.launcher3.util.window.WindowManagerProxy;
-import com.android.quickstep.LauncherActivityInterface;
 
 import java.util.List;
 import java.util.Set;
@@ -40,8 +39,17 @@
  */
 public class SystemWindowManagerProxy extends WindowManagerProxy {
 
+    private final TISBindHelper mTISBindHelper;
+
     public SystemWindowManagerProxy(Context context) {
         super(true);
+        mTISBindHelper = new TISBindHelper(context, binder -> {});
+    }
+
+    @Override
+    public void close() {
+        super.close();
+        mTISBindHelper.onDestroy();
     }
 
     @Override
@@ -53,7 +61,7 @@
     @Override
     public boolean isInDesktopMode() {
         DesktopVisibilityController desktopController =
-                LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
+                mTISBindHelper.getDesktopVisibilityController();
         return desktopController != null && desktopController.areDesktopTasksVisible();
     }
 
diff --git a/quickstep/src/com/android/quickstep/util/TISBindHelper.java b/quickstep/src/com/android/quickstep/util/TISBindHelper.java
index 9a01042..b573604 100644
--- a/quickstep/src/com/android/quickstep/util/TISBindHelper.java
+++ b/quickstep/src/com/android/quickstep/util/TISBindHelper.java
@@ -25,6 +25,7 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.statehandlers.DesktopVisibilityController;
 import com.android.launcher3.taskbar.TaskbarManager;
 import com.android.quickstep.OverviewCommandHelper;
 import com.android.quickstep.TouchInteractionService;
@@ -108,6 +109,11 @@
         return mBinder == null ? null : mBinder.getTaskbarManager();
     }
 
+    @Nullable
+    public DesktopVisibilityController getDesktopVisibilityController() {
+        return mBinder == null ? null : mBinder.getDesktopVisibilityController();
+    }
+
     /**
      * Sets flag whether a predictive back-to-home animation is in progress
      */
diff --git a/quickstep/src/com/android/quickstep/util/TaskGridNavHelper.java b/quickstep/src/com/android/quickstep/util/TaskGridNavHelper.java
index 98d363e..498078b 100644
--- a/quickstep/src/com/android/quickstep/util/TaskGridNavHelper.java
+++ b/quickstep/src/com/android/quickstep/util/TaskGridNavHelper.java
@@ -22,13 +22,13 @@
 import com.android.launcher3.util.IntArray;
 
 import java.lang.annotation.Retention;
+import java.util.List;
 
 /**
  * Helper class for navigating RecentsView grid tasks via arrow keys and tab.
  */
 public class TaskGridNavHelper {
     public static final int CLEAR_ALL_PLACEHOLDER_ID = -1;
-    public static final int INVALID_FOCUSED_TASK_ID = -1;
 
     public static final int DIRECTION_UP = 0;
     public static final int DIRECTION_DOWN = 1;
@@ -43,25 +43,25 @@
     private final IntArray mOriginalTopRowIds;
     private IntArray mTopRowIds;
     private IntArray mBottomRowIds;
-    private final int mFocusedTaskId;
 
-    public TaskGridNavHelper(IntArray topIds, IntArray bottomIds, int focusedTaskId) {
-        mFocusedTaskId = focusedTaskId;
+    public TaskGridNavHelper(IntArray topIds, IntArray bottomIds,
+            List<Integer> largeTileIds) {
         mOriginalTopRowIds = topIds.clone();
-        generateTaskViewIdGrid(topIds, bottomIds);
+        generateTaskViewIdGrid(topIds, bottomIds, largeTileIds);
     }
 
-    private void generateTaskViewIdGrid(IntArray topRowIdArray, IntArray bottomRowIdArray) {
-        boolean hasFocusedTask = mFocusedTaskId != INVALID_FOCUSED_TASK_ID;
-        int maxSize =
-                Math.max(topRowIdArray.size(), bottomRowIdArray.size()) + (hasFocusedTask ? 1 : 0);
-        int minSize =
-                Math.min(topRowIdArray.size(), bottomRowIdArray.size()) + (hasFocusedTask ? 1 : 0);
+    private void generateTaskViewIdGrid(IntArray topRowIdArray, IntArray bottomRowIdArray,
+            List<Integer> largeTileIds) {
 
-        // Add the focused task to the beginning of both arrays if it exists.
-        if (hasFocusedTask) {
-            topRowIdArray.add(0, mFocusedTaskId);
-            bottomRowIdArray.add(0, mFocusedTaskId);
+        int maxSize = Math.max(topRowIdArray.size(), bottomRowIdArray.size())
+                + largeTileIds.size();
+        int minSize = Math.min(topRowIdArray.size(), bottomRowIdArray.size())
+                + largeTileIds.size();
+
+        // Add Large tile task views first at the beginning
+        for (int i = 0; i < largeTileIds.size(); i++) {
+            topRowIdArray.add(i, largeTileIds.get(i));
+            bottomRowIdArray.add(i, largeTileIds.get(i));
         }
 
         // Fill in the shorter array with the ids from the longer one.
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 709e0aa..bb46a2c 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -681,6 +681,7 @@
     protected int mRunningTaskViewId = -1;
     private int mTaskViewIdCount;
     protected boolean mRunningTaskTileHidden;
+    private boolean mNonRunningTaskCategoryHidden;
     @Nullable
     private Task[] mTmpRunningTasks;
     protected int mFocusedTaskViewId = INVALID_TASK_ID;
@@ -841,8 +842,7 @@
 
     private final RecentsViewModel mRecentsViewModel;
     private final RecentsViewModelHelper mHelper;
-
-    private final RecentsViewUtils mRecentsViewUtils = new RecentsViewUtils();
+    private final RecentsViewUtils mUtils = new RecentsViewUtils();
 
     public RecentsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
             BaseContainerInterface sizeStrategy) {
@@ -1103,14 +1103,13 @@
 
     @Override
     public void onTaskIconChanged(@NonNull String pkg, @NonNull UserHandle user) {
-        for (int i = 0; i < getTaskViewCount(); i++) {
-            TaskView tv = requireTaskViewAt(i);
-            Task task = tv.getFirstTask();
+        for (TaskView taskView : getTaskViews()) {
+            Task task = taskView.getFirstTask();
             if (pkg.equals(task.key.getPackageName()) && task.key.userId == user.getIdentifier()) {
                 task.icon = null;
-                if (tv.getTaskContainers().stream().anyMatch(
+                if (taskView.getTaskContainers().stream().anyMatch(
                         container -> container.getIconView().getDrawable() != null)) {
-                    tv.onTaskListVisibilityChanged(true /* visible */);
+                    taskView.onTaskListVisibilityChanged(true /* visible */);
                 }
             }
         }
@@ -1499,8 +1498,7 @@
             return null;
         }
 
-        for (int i = 0; i < getTaskViewCount(); i++) {
-            TaskView taskView = requireTaskViewAt(i);
+        for (TaskView taskView : getTaskViews()) {
             if (taskView.containsTaskId(taskId)) {
                 return taskView;
             }
@@ -1521,8 +1519,7 @@
         int[] taskIdsCopy = Arrays.copyOf(taskIds, taskIds.length);
         Arrays.sort(taskIdsCopy);
 
-        for (int i = 0; i < getTaskViewCount(); i++) {
-            TaskView taskView = requireTaskViewAt(i);
+        for (TaskView taskView : getTaskViews()) {
             int[] taskViewIdsCopy = taskView.getTaskIds();
             Arrays.sort(taskViewIdsCopy);
             if (Arrays.equals(taskIdsCopy, taskViewIdsCopy)) {
@@ -1558,9 +1555,7 @@
      */
     public void setTaskBorderEnabled(boolean enabled) {
         mBorderEnabled = enabled;
-        int taskCount = getTaskViewCount();
-        for (int i = 0; i < taskCount; i++) {
-            TaskView taskView = requireTaskViewAt(i);
+        for (TaskView taskView : getTaskViews()) {
             taskView.setBorderEnabled(enabled);
         }
         mClearAllButton.setBorderEnabled(enabled);
@@ -1627,9 +1622,7 @@
         super.onTouchEvent(ev);
 
         if (showAsGrid()) {
-            int taskCount = getTaskViewCount();
-            for (int i = 0; i < taskCount; i++) {
-                TaskView taskView = requireTaskViewAt(i);
+            for (TaskView taskView : getTaskViews()) {
                 if (isTaskViewVisible(taskView) && taskView.offerTouchToChildren(ev)) {
                     // Keep consuming events to pass to delegate
                     return true;
@@ -1883,7 +1876,7 @@
 
         // Move Desktop Tasks to the end of the list
         if (enableLargeDesktopWindowingTile()) {
-            taskGroups = mRecentsViewUtils.sortDesktopTasksToFront(taskGroups);
+            taskGroups = mUtils.sortDesktopTasksToFront(taskGroups);
         }
 
         // Add views as children based on whether it's grouped or single task. Looping through
@@ -1939,7 +1932,7 @@
         // Keep same previous focused task
         TaskView newFocusedTaskView = getTaskViewByTaskIds(focusedTaskIds);
         // If the list changed, maybe the focused task doesn't exist anymore
-        int newFocusedTaskViewIndex = mRecentsViewUtils.getFocusedTaskIndex(taskGroups);
+        int newFocusedTaskViewIndex = mUtils.getFocusedTaskIndex(taskGroups);
         if (newFocusedTaskView == null && getTaskViewCount() > newFocusedTaskViewIndex) {
             newFocusedTaskView = getTaskViewAt(newFocusedTaskViewIndex);
         }
@@ -2030,15 +2023,13 @@
     }
 
     private void removeTasksViewsAndClearAllButton() {
-        // This handles an edge case where applyLoadPlan happens during a gesture when the
-        // only Task is one with excludeFromRecents, in which case we should not remove it.
-        final int stubRunningTaskIndex = isGestureActive() ? getRunningTaskIndex() : -1;
-
-        for (int i = getTaskViewCount() - 1; i >= 0; i--) {
-            if (i == stubRunningTaskIndex) {
+        for (TaskView taskView : getTaskViews()) {
+            if (isGestureActive() && taskView.isRunningTask()) {
+                // This handles an edge case where applyLoadPlan happens during a gesture when the
+                // only Task is one with excludeFromRecents, in which case we should not remove it.
                 continue;
             }
-            removeView(requireTaskViewAt(i));
+            removeView(taskView);
         }
         if (getTaskViewCount() == 0 && indexOfChild(mClearAllButton) != -1) {
             removeView(mClearAllButton);
@@ -2059,7 +2050,7 @@
      * @return Number of children that are instances of DesktopTaskView
      */
     private int getDesktopTaskViewCount() {
-        return mRecentsViewUtils.getDesktopTaskViewCount(getChildCount(), this::getTaskViewAt);
+        return mUtils.getDesktopTaskViewCount(getTaskViews());
     }
 
     /**
@@ -2082,8 +2073,7 @@
     }
 
     public void resetTaskVisuals() {
-        for (int i = getTaskViewCount() - 1; i >= 0; i--) {
-            TaskView taskView = requireTaskViewAt(i);
+        for (TaskView taskView : getTaskViews()) {
             if (Arrays.stream(taskView.getTaskIds()).noneMatch(
                     taskId -> taskId == mIgnoreResetTaskId)) {
                 taskView.resetViewTransforms();
@@ -2105,14 +2095,10 @@
             simulator.fullScreenProgress.value = 0;
             simulator.recentsViewScale.value = 1;
         });
-        // Similar to setRunningTaskHidden below, reapply the state before runningTaskView is
-        // null.
-        if (!mRunningTaskShowScreenshot) {
-            setRunningTaskViewShowScreenshot(mRunningTaskShowScreenshot);
-        }
-        if (mRunningTaskTileHidden) {
-            setRunningTaskHidden(mRunningTaskTileHidden);
-        }
+        // Reapply runningTask related attributes as they might have been reset by
+        // resetViewTransforms().
+        setRunningTaskViewShowScreenshot(mRunningTaskShowScreenshot);
+        applyAttachAlpha();
 
         updateCurveProperties();
         // Update the set of visible task's data
@@ -2126,9 +2112,8 @@
         if (enableRefactorTaskThumbnail()) {
             mRecentsViewModel.updateFullscreenProgress(mFullscreenProgress);
         }
-        int taskCount = getTaskViewCount();
-        for (int i = 0; i < taskCount; i++) {
-            requireTaskViewAt(i).setFullscreenProgress(mFullscreenProgress);
+        for (TaskView taskView : getTaskViews()) {
+            taskView.setFullscreenProgress(mFullscreenProgress);
         }
         mClearAllButton.setFullscreenProgress(fullscreenProgress);
 
@@ -2274,8 +2259,7 @@
                     ? mLastComputedCarouselTaskSize.right - mLastComputedTaskSize.right
                     : mLastComputedCarouselTaskSize.left - mLastComputedTaskSize.left;
         }
-        for (int i = 0; i < taskCount; i++) {
-            TaskView taskView = requireTaskViewAt(i);
+        for (TaskView taskView : getTaskViews()) {
             taskView.updateTaskSize(mLastComputedTaskSize, mLastComputedGridTaskSize,
                     mLastComputedCarouselTaskSize);
             taskView.setNonGridTranslationX(accumulatedTranslationX);
@@ -2663,18 +2647,13 @@
         return getTaskViewFromTaskViewId(mFocusedTaskViewId);
     }
 
-    private @Nullable TaskView getFirstLargeTaskView() {
-        return mRecentsViewUtils.getFirstLargeTaskView(getChildCount(), this::getTaskViewAt);
-    }
-
     @Nullable
     private TaskView getTaskViewFromTaskViewId(int taskViewId) {
         if (taskViewId == -1) {
             return null;
         }
 
-        for (int i = 0; i < getTaskViewCount(); i++) {
-            TaskView taskView = requireTaskViewAt(i);
+        for (TaskView taskView : getTaskViews()) {
             if (taskView.getTaskViewId() == taskViewId) {
                 return taskView;
             }
@@ -2763,7 +2742,10 @@
         showCurrentTask(mActiveGestureRunningTasks);
         setEnableFreeScroll(false);
         setEnableDrawingLiveTile(false);
-        setRunningTaskHidden(!shouldUpdateRunningTaskAlpha());
+        setRunningTaskHidden(true);
+        if (enableLargeDesktopWindowingTile()) {
+            setNonRunningTaskCategoryHidden(true);
+        }
         setTaskIconScaledDown(true);
     }
 
@@ -2823,8 +2805,8 @@
     }
 
     private void updateChildTaskOrientations() {
-        for (int i = 0; i < getTaskViewCount(); i++) {
-            requireTaskViewAt(i).setOrientationState(mOrientationState);
+        for (TaskView taskView : getTaskViews()) {
+            taskView.setOrientationState(mOrientationState);
         }
         boolean shouldRotateMenuForFakeRotation =
                 !mOrientationState.isRecentsActivityRotationAllowed();
@@ -2902,6 +2884,9 @@
         setEnableDrawingLiveTile(mCurrentGestureEndTarget == GestureState.GestureEndTarget.RECENTS);
         Log.d(TAG, "onGestureAnimationEnd - mEnableDrawingLiveTile: " + mEnableDrawingLiveTile);
         setRunningTaskHidden(false);
+        if (enableLargeDesktopWindowingTile()) {
+            setNonRunningTaskCategoryHidden(false);
+        }
         animateUpTaskIconScale();
         animateActionsViewIn();
 
@@ -3057,13 +3042,27 @@
         if (runningTask == null) {
             return;
         }
-        runningTask.setStableAlpha(isHidden ? 0 : mContentAlpha);
+        applyAttachAlpha();
         if (!isHidden) {
             AccessibilityManagerCompat.sendCustomAccessibilityEvent(
                     runningTask, AccessibilityEvent.TYPE_VIEW_FOCUSED, null);
         }
     }
 
+    /**
+     * Hides the tasks that has a different category (Fullscreen/Desktop) from the running task.
+     */
+    public void setNonRunningTaskCategoryHidden(boolean isHidden) {
+        mNonRunningTaskCategoryHidden = isHidden;
+        updateMinAndMaxScrollX();
+        applyAttachAlpha();
+    }
+
+    private void applyAttachAlpha() {
+        mUtils.applyAttachAlpha(getTaskViews(), getRunningTaskView(), mRunningTaskTileHidden,
+                mNonRunningTaskCategoryHidden);
+    }
+
     private void setRunningTaskViewShowScreenshot(boolean showScreenshot) {
         setRunningTaskViewShowScreenshot(showScreenshot, /*updatedThumbnails=*/null);
     }
@@ -3083,9 +3082,8 @@
     public void setTaskIconScaledDown(boolean isScaledDown) {
         if (mTaskIconScaledDown != isScaledDown) {
             mTaskIconScaledDown = isScaledDown;
-            int taskCount = getTaskViewCount();
-            for (int i = 0; i < taskCount; i++) {
-                requireTaskViewAt(i).setIconScaleAndDim(mTaskIconScaledDown ? 0 : 1);
+            for (TaskView taskView : getTaskViews()) {
+                taskView.setIconScaleAndDim(mTaskIconScaledDown ? 0 : 1);
             }
         }
     }
@@ -3098,9 +3096,7 @@
 
     public void animateUpTaskIconScale() {
         mTaskIconScaledDown = false;
-        int taskCount = getTaskViewCount();
-        for (int i = 0; i < taskCount; i++) {
-            TaskView taskView = requireTaskViewAt(i);
+        for (TaskView taskView : getTaskViews()) {
             taskView.animateIconScaleAndDimIntoView();
         }
     }
@@ -3395,9 +3391,8 @@
     private void setGridProgress(float gridProgress) {
         mGridProgress = gridProgress;
 
-        int taskCount = getTaskViewCount();
-        for (int i = 0; i < taskCount; i++) {
-            requireTaskViewAt(i).setGridProgress(gridProgress);
+        for (TaskView taskView : getTaskViews()) {
+            taskView.setGridProgress(gridProgress);
         }
         mClearAllButton.setGridProgress(gridProgress);
     }
@@ -3407,14 +3402,10 @@
             mRecentsViewModel.updateThumbnailSplashProgress(taskThumbnailSplashAlpha);
             return;
         }
-        int taskCount = getTaskViewCount();
-        if (taskCount == 0) {
-            return;
-        }
 
         mTaskThumbnailSplashAlpha = taskThumbnailSplashAlpha;
-        for (int i = 0; i < taskCount; i++) {
-            requireTaskViewAt(i).setTaskThumbnailSplashAlpha(taskThumbnailSplashAlpha);
+        for (TaskView taskView : getTaskViews()) {
+            taskView.setTaskThumbnailSplashAlpha(taskThumbnailSplashAlpha);
         }
     }
 
@@ -3637,8 +3628,7 @@
                     nextFocusedTaskFromTop =
                             !mTopRowIdSet.isEmpty() && mTopRowIdSet.size() >= (taskCount - 1) / 2f;
                     // Pick the next focused task from the preferred row.
-                    for (int i = 0; i < taskCount; i++) {
-                        TaskView taskView = requireTaskViewAt(i);
+                    for (TaskView taskView : getTaskViews()) {
                         if (taskView == dismissedTaskView || taskView.isLargeTile()) {
                             continue;
                         }
@@ -3766,8 +3756,7 @@
                         END_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
                                 + (taskCount - 1) * halfAdditionalDismissTranslationOffset,
                         END_DISMISS_TRANSLATION_INTERPOLATION_OFFSET, 1);
-                for (int i = 0; i < taskCount; i++) {
-                    TaskView taskView = requireTaskViewAt(i);
+                for (TaskView taskView : getTaskViews()) {
                     anim.setFloat(taskView, TaskView.GRID_END_TRANSLATION_X, longGridRowWidthDiff,
                             clampToProgress(LINEAR, dismissTranslationInterpolationEnd, 1));
                     dismissTranslationInterpolationEnd = Utilities.boundToRange(
@@ -4227,9 +4216,8 @@
             return new IntArray(0);
         }
         IntArray topArray = new IntArray(mTopRowIdSet.size());
-        int taskViewCount = getTaskViewCount();
-        for (int i = 0; i < taskViewCount; i++) {
-            int taskViewId = requireTaskViewAt(i).getTaskViewId();
+        for (TaskView taskView : getTaskViews()) {
+            int taskViewId = taskView.getTaskViewId();
             if (mTopRowIdSet.contains(taskViewId)) {
                 topArray.add(taskViewId);
             }
@@ -4246,9 +4234,7 @@
             return new IntArray(0);
         }
         IntArray bottomArray = new IntArray(bottomRowIdArraySize);
-        int taskViewCount = getTaskViewCount();
-        for (int i = 0; i < taskViewCount; i++) {
-            TaskView taskView = requireTaskViewAt(i);
+        for (TaskView taskView : getTaskViews()) {
             int taskViewId = taskView.getTaskViewId();
             if (!mTopRowIdSet.contains(taskViewId) && !taskView.isLargeTile()) {
                 bottomArray.add(taskViewId);
@@ -4308,9 +4294,8 @@
         }
         PendingAnimation anim = new PendingAnimation(duration);
 
-        int count = getTaskViewCount();
-        for (int i = 0; i < count; i++) {
-            addDismissedTaskAnimations(requireTaskViewAt(i), duration, anim);
+        for (TaskView taskView : getTaskViews()) {
+            addDismissedTaskAnimations(taskView, duration, anim);
         }
 
         mPendingAnimation = anim;
@@ -4356,9 +4341,8 @@
         }
 
         // Init task grid nav helper with top/bottom id arrays.
-        // TODO(b/361070854): Add keyboard navigation for all large tiles.
         TaskGridNavHelper taskGridNavHelper = new TaskGridNavHelper(getTopRowIdArray(),
-                getBottomRowIdArray(), mFocusedTaskViewId);
+                getBottomRowIdArray(), mUtils.getLargeTaskViewIds(getTaskViews()));
 
         // Get current page's task view ID.
         TaskView currentPageTaskView = getCurrentPageTaskView();
@@ -4476,13 +4460,8 @@
         alpha = Utilities.boundToRange(alpha, 0, 1);
         mContentAlpha = alpha;
 
-        TaskView runningTaskView = getRunningTaskView();
-        for (int i = getTaskViewCount() - 1; i >= 0; i--) {
-            TaskView child = requireTaskViewAt(i);
-            if (runningTaskView != null && mRunningTaskTileHidden && child == runningTaskView) {
-                continue;
-            }
-            child.setStableAlpha(alpha);
+        for (TaskView taskView : getTaskViews()) {
+            taskView.setStableAlpha(alpha);
         }
         mClearAllButton.setContentAlpha(mContentAlpha);
         int alphaInt = Math.round(alpha * 255);
@@ -4591,6 +4570,13 @@
         return Objects.requireNonNull(getTaskViewAt(index));
     }
 
+    /**
+     * Returns the current list of [TaskView] children.
+     */
+    private Iterable<TaskView> getTaskViews() {
+        return mUtils.getTaskViews(getTaskViewCount(), this::requireTaskViewAt);
+    }
+
     public void setOnEmptyMessageUpdatedListener(OnEmptyMessageUpdatedListener listener) {
         mOnEmptyMessageUpdatedListener = listener;
     }
@@ -4895,9 +4881,9 @@
 
     protected void setTaskViewsResistanceTranslation(float translation) {
         mTaskViewsSecondaryTranslation = translation;
-        for (int i = 0; i < getTaskViewCount(); i++) {
-            TaskView task = requireTaskViewAt(i);
-            task.getTaskResistanceTranslationProperty().set(task, translation / getScaleY());
+        for (TaskView taskView : getTaskViews()) {
+            taskView.getTaskResistanceTranslationProperty().set(taskView,
+                    translation / getScaleY());
         }
         runActionOnRemoteHandles(
                 remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator()
@@ -4905,23 +4891,21 @@
     }
 
     private void updateTaskViewsSnapshotRadius() {
-        for (int i = 0; i < getTaskViewCount(); i++) {
-            requireTaskViewAt(i).updateSnapshotRadius();
+        for (TaskView taskView : getTaskViews()) {
+            taskView.updateSnapshotRadius();
         }
     }
 
     protected void setTaskViewsPrimarySplitTranslation(float translation) {
         mTaskViewsPrimarySplitTranslation = translation;
-        for (int i = 0; i < getTaskViewCount(); i++) {
-            TaskView task = requireTaskViewAt(i);
-            task.getPrimarySplitTranslationProperty().set(task, translation);
+        for (TaskView taskView : getTaskViews()) {
+            taskView.getPrimarySplitTranslationProperty().set(taskView, translation);
         }
     }
 
     protected void setTaskViewsSecondarySplitTranslation(float translation) {
         mTaskViewsSecondarySplitTranslation = translation;
-        for (int i = 0; i < getTaskViewCount(); i++) {
-            TaskView taskView = requireTaskViewAt(i);
+        for (TaskView taskView : getTaskViews()) {
             if (taskView == mSplitHiddenTaskView && !taskView.containsMultipleTasks()) {
                 continue;
             }
@@ -5819,26 +5803,42 @@
     }
 
     private int getFirstViewIndex() {
-        TaskView firstTaskView = mShowAsGridLastOnLayout ? getFirstLargeTaskView() : null;
-        return firstTaskView != null ? indexOfChild(firstTaskView) : 0;
+        final TaskView firstView;
+        if (mShowAsGridLastOnLayout) {
+            // For grid Overivew, it always start if a large tile (focused task or desktop task) if
+            // they exist, otherwise it start with the first task.
+            TaskView firstLargeTaskView = mUtils.getFirstLargeTaskView(getTaskViews());
+            if (firstLargeTaskView != null) {
+                firstView = firstLargeTaskView;
+            } else {
+                firstView = getTaskViewAt(0);
+            }
+        } else {
+            firstView = mUtils.getFirstTaskViewInCarousel(mNonRunningTaskCategoryHidden,
+                    getTaskViews(), getRunningTaskView());
+        }
+        return indexOfChild(firstView);
     }
 
     private int getLastViewIndex() {
+        final View lastView;
         if (!mDisallowScrollToClearAll) {
-            return indexOfChild(mClearAllButton);
+            // When ClearAllButton is present, it always end with ClearAllButton.
+            lastView = mClearAllButton;
+        } else if (mShowAsGridLastOnLayout) {
+            // When ClearAllButton is absent, for the grid Overview, it always end with a grid task
+            // if they exist, otherwise it ends with a large tile (focused task or desktop task).
+            TaskView lastGridTaskView = getLastGridTaskView();
+            if (lastGridTaskView != null) {
+                lastView = lastGridTaskView;
+            } else {
+                lastView = mUtils.getLastLargeTaskView(getTaskViews());
+            }
+        } else {
+            lastView = mUtils.getLastTaskViewInCarousel(mNonRunningTaskCategoryHidden,
+                    getTaskViews(), getRunningTaskView());
         }
-
-        if (!mShowAsGridLastOnLayout) {
-            return getTaskViewCount() - 1;
-        }
-
-        TaskView lastGridTaskView = getLastGridTaskView();
-        if (lastGridTaskView != null) {
-            return indexOfChild(lastGridTaskView);
-        }
-
-        // Returns focus task if there are no grid tasks.
-        return indexOfChild(getFirstLargeTaskView());
+        return indexOfChild(lastView);
     }
 
     /**
@@ -6088,9 +6088,7 @@
 
     private void updateEnabledOverlays() {
         TaskView focusedTaskView = getFocusedTaskView();
-        int taskCount = getTaskViewCount();
-        for (int i = 0; i < taskCount; i++) {
-            TaskView taskView = requireTaskViewAt(i);
+        for (TaskView taskView : getTaskViews()) {
             if (taskView == focusedTaskView) {
                 continue;
             }
@@ -6164,7 +6162,7 @@
             return;
         }
 
-        Map<Integer, ThumbnailData> updatedThumbnails = mRecentsViewUtils.screenshotTasks(taskView,
+        Map<Integer, ThumbnailData> updatedThumbnails = mUtils.screenshotTasks(taskView,
                 mRecentsAnimationController);
         if (enableRefactorTaskThumbnail()) {
             mHelper.switchToScreenshot(taskView, updatedThumbnails, onFinishRunnable);
@@ -6262,8 +6260,8 @@
             mRecentsViewModel.setTintAmount(tintAmount);
         }
 
-        for (int i = 0; i < getTaskViewCount(); i++) {
-            requireTaskViewAt(i).setColorTint(mColorTint, mTintingColor);
+        for (TaskView taskView : getTaskViews()) {
+            taskView.setColorTint(mColorTint, mTintingColor);
         }
 
         Drawable scrimBg = mContainer.getScrimView().getBackground();
diff --git a/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java b/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java
index 060c71e..8f19444 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java
@@ -26,8 +26,11 @@
 import android.view.View;
 import android.view.Window;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.logger.LauncherAtom;
+import com.android.launcher3.statehandlers.DesktopVisibilityController;
 import com.android.launcher3.util.SystemUiController;
 import com.android.launcher3.views.ActivityContext;
 import com.android.launcher3.views.ScrimView;
@@ -198,4 +201,7 @@
                                         .setOrientationHandler(orientationForLogging))
                         .build());
     }
+
+    @Nullable
+    DesktopVisibilityController getDesktopVisibilityController();
 }
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index 601dae8..4dcf2e7 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -84,7 +84,6 @@
 import com.android.quickstep.orientation.RecentsPagedOrientationHandler
 import com.android.quickstep.recents.di.RecentsDependencies
 import com.android.quickstep.recents.di.get
-import com.android.quickstep.task.thumbnail.TaskThumbnailView
 import com.android.quickstep.task.viewmodel.TaskViewModel
 import com.android.quickstep.util.ActiveGestureErrorDetector
 import com.android.quickstep.util.ActiveGestureLog
@@ -201,14 +200,14 @@
         get() =
             pagedOrientationHandler.getPrimaryValue(
                 SPLIT_SELECT_TRANSLATION_X,
-                SPLIT_SELECT_TRANSLATION_Y
+                SPLIT_SELECT_TRANSLATION_Y,
             )
 
     protected val secondarySplitTranslationProperty: FloatProperty<TaskView>
         get() =
             pagedOrientationHandler.getSecondaryValue(
                 SPLIT_SELECT_TRANSLATION_X,
-                SPLIT_SELECT_TRANSLATION_Y
+                SPLIT_SELECT_TRANSLATION_Y,
             )
 
     protected val primaryDismissTranslationProperty: FloatProperty<TaskView>
@@ -223,21 +222,21 @@
         get() =
             pagedOrientationHandler.getPrimaryValue(
                 TASK_OFFSET_TRANSLATION_X,
-                TASK_OFFSET_TRANSLATION_Y
+                TASK_OFFSET_TRANSLATION_Y,
             )
 
     protected val secondaryTaskOffsetTranslationProperty: FloatProperty<TaskView>
         get() =
             pagedOrientationHandler.getSecondaryValue(
                 TASK_OFFSET_TRANSLATION_X,
-                TASK_OFFSET_TRANSLATION_Y
+                TASK_OFFSET_TRANSLATION_Y,
             )
 
     protected val taskResistanceTranslationProperty: FloatProperty<TaskView>
         get() =
             pagedOrientationHandler.getSecondaryValue(
                 TASK_RESISTANCE_TRANSLATION_X,
-                TASK_RESISTANCE_TRANSLATION_Y
+                TASK_RESISTANCE_TRANSLATION_Y,
             )
 
     private val tempCoordinates = FloatArray(2)
@@ -399,7 +398,7 @@
         }
         get() = taskViewAlpha.get(ALPHA_INDEX_STABLE).value
 
-    protected var attachAlpha
+    var attachAlpha
         set(value) {
             taskViewAlpha.get(ALPHA_INDEX_ATTACH).value = value
         }
@@ -435,7 +434,7 @@
             field = value
             Log.d(
                 TAG,
-                "${taskIds.contentToString()} - setting border animator visibility to: $field"
+                "${taskIds.contentToString()} - setting border animator visibility to: $field",
             )
             hoverBorderAnimator?.setBorderVisibility(visible = field, animated = true)
         }
@@ -455,7 +454,7 @@
             FOCUS_TRANSITION,
             FOCUS_TRANSITION_INDEX_COUNT,
             { x: Float, y: Float -> x * y },
-            1f
+            1f,
         )
     private val focusTransitionFullscreen =
         focusTransitionPropertyFactory.get(FOCUS_TRANSITION_INDEX_FULLSCREEN)
@@ -503,8 +502,8 @@
                             this,
                             it.getColor(
                                 R.styleable.TaskView_focusBorderColor,
-                                BorderAnimator.DEFAULT_BORDER_COLOR
-                            )
+                                BorderAnimator.DEFAULT_BORDER_COLOR,
+                            ),
                         )
                     else null
             this.hoverBorderAnimator =
@@ -519,8 +518,8 @@
                             this,
                             it.getColor(
                                 R.styleable.TaskView_hoverBorderColor,
-                                BorderAnimator.DEFAULT_BORDER_COLOR
-                            )
+                                BorderAnimator.DEFAULT_BORDER_COLOR,
+                            ),
                         )
                     else null
         }
@@ -634,7 +633,7 @@
             addAction(
                 AccessibilityAction(
                     R.id.action_close,
-                    context.getText(R.string.accessibility_close)
+                    context.getText(R.string.accessibility_close),
                 )
             )
 
@@ -658,7 +657,7 @@
                         1,
                         it.taskViewCount - it.indexOfChild(this@TaskView) - 1,
                         1,
-                        false
+                        false,
                     )
             }
         }
@@ -704,7 +703,7 @@
                     R.id.show_windows,
                     R.id.digital_wellbeing_toast,
                     STAGE_POSITION_UNDEFINED,
-                    taskOverlayFactory
+                    taskOverlayFactory,
                 )
             )
         taskContainers.forEach { it.bind() }
@@ -742,7 +741,7 @@
             stagePosition,
             digitalWellBeingToast,
             findViewById(showWindowViewId)!!,
-            taskOverlayFactory
+            taskOverlayFactory,
         )
     }
 
@@ -860,7 +859,7 @@
             if (relativeToDragLayer) {
                 container.dragLayer.getDescendantRectRelativeToSelf(
                     it.snapshotView,
-                    thumbnailBounds
+                    thumbnailBounds,
                 )
             } else {
                 thumbnailBounds.set(it.snapshotView)
@@ -979,7 +978,7 @@
     @JvmOverloads
     open fun setShouldShowScreenshot(
         shouldShowScreenshot: Boolean,
-        thumbnailDatas: Map<Int, ThumbnailData?>? = null
+        thumbnailDatas: Map<Int, ThumbnailData?>? = null,
     ) {
         if (this.shouldShowScreenshot == shouldShowScreenshot) return
         this.shouldShowScreenshot = shouldShowScreenshot
@@ -1030,7 +1029,7 @@
         if (!isClickableAsLiveTile) {
             Log.e(
                 TAG,
-                "launchAsLiveTile - TaskView is not clickable as a live tile; returning to home: ${taskIds.contentToString()}"
+                "launchAsLiveTile - TaskView is not clickable as a live tile; returning to home: ${taskIds.contentToString()}",
             )
             return null
         }
@@ -1049,7 +1048,7 @@
                     apps.toTypedArray(),
                     wallpapers.toTypedArray(),
                     remoteTargetHandles[0].transformParams.targetSet.nonApps,
-                    remoteTargetHandles[0].transformParams.targetSet.targetMode
+                    remoteTargetHandles[0].transformParams.targetSet.targetMode,
                 )
             }
         if (targets == null) {
@@ -1059,7 +1058,7 @@
             if (runnableList == null) {
                 Log.e(
                     TAG,
-                    "launchAsLiveTile - Recents animation cancelled and cannot launch task as non-live tile; returning to home: ${taskIds.contentToString()}"
+                    "launchAsLiveTile - Recents animation cancelled and cannot launch task as non-live tile; returning to home: ${taskIds.contentToString()}",
                 )
             }
             isClickableAsLiveTile = true
@@ -1068,7 +1067,7 @@
         TestLogging.recordEvent(
             TestProtocol.SEQUENCE_MAIN,
             "composeRecentsLaunchAnimator",
-            taskIds.contentToString()
+            taskIds.contentToString(),
         )
         val runnableList = RunnableList()
         with(AnimatorSet()) {
@@ -1081,7 +1080,7 @@
                 true /* launcherClosing */,
                 recentsView.stateManager,
                 recentsView,
-                recentsView.depthController
+                recentsView.depthController,
             )
             addListener(
                 object : AnimatorListenerAdapter() {
@@ -1118,7 +1117,7 @@
         TestLogging.recordEvent(
             TestProtocol.SEQUENCE_MAIN,
             "startActivityFromRecentsAsync",
-            taskIds.contentToString()
+            taskIds.contentToString(),
         )
         val opts =
             container.getActivityLaunchOptions(this, null).apply {
@@ -1130,7 +1129,7 @@
         ) {
             Log.d(
                 TAG,
-                "launchAsStaticTile - startActivityFromRecents: ${taskIds.contentToString()}"
+                "launchAsStaticTile - startActivityFromRecents: ${taskIds.contentToString()}",
             )
             ActiveGestureLog.INSTANCE.trackEvent(
                 ActiveGestureErrorDetector.GestureEvent.EXPECTING_TASK_APPEARED
@@ -1163,12 +1162,12 @@
     @JvmOverloads
     open fun launchWithoutAnimation(
         isQuickSwitch: Boolean = false,
-        callback: (launched: Boolean) -> Unit
+        callback: (launched: Boolean) -> Unit,
     ) {
         TestLogging.recordEvent(
             TestProtocol.SEQUENCE_MAIN,
             "startActivityFromRecentsAsync",
-            taskIds.contentToString()
+            taskIds.contentToString(),
         )
         val firstContainer = taskContainers[0]
         val failureListener = TaskRemovedDuringLaunchListener(context.applicationContext)
@@ -1199,7 +1198,7 @@
                     0,
                     0,
                     Executors.MAIN_EXECUTOR.handler,
-                    { callback(true) }
+                    { callback(true) },
                 ) {
                     failureListener.onTransitionFinished()
                 }
@@ -1227,7 +1226,7 @@
             }
             Log.d(
                 TAG,
-                "launchWithoutAnimation - startActivityFromRecents: ${taskIds.contentToString()}"
+                "launchWithoutAnimation - startActivityFromRecents: ${taskIds.contentToString()}",
             )
         }
     }
@@ -1246,7 +1245,7 @@
         recentsView?.initiateSplitSelect(
             this,
             splitPositionOption.stagePosition,
-            SplitConfigurationOptions.getLogEventForPosition(splitPositionOption.stagePosition)
+            SplitConfigurationOptions.getLogEventForPosition(splitPositionOption.stagePosition),
         )
     }
 
@@ -1269,7 +1268,7 @@
             container.splitAnimationThumbnail,
             /* intent */ null,
             /* user */ null,
-            container.itemInfo
+            container.itemInfo,
         )
     }
 
@@ -1374,13 +1373,13 @@
                 this[0] = viewHalfWidth
                 this[1] = viewHalfHeight
             },
-            false
+            false,
         )
         transformingTouchDelegate.setBounds(
             (tempCenterCoordinates[0] - viewHalfWidth).toInt(),
             (tempCenterCoordinates[1] - viewHalfHeight).toInt(),
             (tempCenterCoordinates[0] + viewHalfWidth).toInt(),
-            (tempCenterCoordinates[1] + viewHalfHeight).toInt()
+            (tempCenterCoordinates[1] + viewHalfHeight).toInt(),
         )
     }
 
@@ -1390,7 +1389,7 @@
             it.showWindowsView?.let { showWindowsView ->
                 updateFilterCallback(
                     showWindowsView,
-                    getFilterUpdateCallback(it.task.key.packageName)
+                    getFilterUpdateCallback(it.task.key.packageName),
                 )
             }
         }
@@ -1593,10 +1592,7 @@
         resetViewTransforms()
     }
 
-    fun getTaskContainerForTaskThumbnailView(taskThumbnailView: TaskThumbnailView): TaskContainer? =
-        taskContainers.firstOrNull { it.thumbnailView == taskThumbnailView }
-
-    open fun resetViewTransforms() {
+    fun resetViewTransforms() {
         // fullscreenTranslation and accumulatedTranslation should not be reset, as
         // resetViewTransforms is called during QuickSwitch scrolling.
         dismissTranslationX = 0f
@@ -1710,7 +1706,7 @@
             Interpolators.clampToProgress(
                 Interpolators.FAST_OUT_SLOW_IN,
                 1f - FOCUS_TRANSITION_THRESHOLD,
-                1f
+                1f,
             )!!
         private val SYSTEM_GESTURE_EXCLUSION_RECT = listOf(Rect())
 
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 262d8da..1d13956 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
@@ -16,6 +16,7 @@
 
 package com.android.launcher3.taskbar.bubbles.stashing
 
+import android.animation.AnimatorSet
 import android.animation.AnimatorTestRule
 import android.content.Context
 import android.view.View
@@ -60,7 +61,7 @@
         const val TASK_BAR_TRANSLATION_Y = -TASKBAR_BOTTOM_SPACE
         const val HANDLE_VIEW_WIDTH = 150
         const val HANDLE_VIEW_HEIGHT = 4
-        const val BUBBLE_BAR_STASHED_TRANSLATION_Y = -2.5f
+        const val BUBBLE_BAR_STASHED_TRANSLATION_Y = -4.5f
     }
 
     @get:Rule val animatorTestRule: AnimatorTestRule = AnimatorTestRule(this)
@@ -80,6 +81,8 @@
     private lateinit var barScaleX: AnimatedFloat
     private lateinit var barScaleY: AnimatedFloat
     private lateinit var barAlpha: MultiValueAlpha
+    private lateinit var bubbleAlpha: AnimatedFloat
+    private lateinit var backgroundAlpha: AnimatedFloat
     private lateinit var stashedHandleAlpha: MultiValueAlpha
     private lateinit var stashedHandleScale: AnimatedFloat
     private lateinit var stashedHandleTranslationY: AnimatedFloat
@@ -299,14 +302,20 @@
         barScaleX = AnimatedFloat { value -> bubbleBarView.scaleX = value }
         barScaleY = AnimatedFloat { value -> bubbleBarView.scaleY = value }
         barAlpha = MultiValueAlpha(bubbleBarView, 1 /* num alpha channels */)
+        bubbleAlpha = AnimatedFloat { value -> bubbleBarView.setBubbleAlpha(value) }
+        backgroundAlpha = AnimatedFloat { value -> bubbleBarView.setBackgroundAlpha(value) }
 
         whenever(bubbleBarViewController.hasBubbles()).thenReturn(true)
         whenever(bubbleBarViewController.bubbleBarTranslationY).thenReturn(barTranslationY)
-        whenever(bubbleBarViewController.bubbleBarScaleX).thenReturn(barScaleX)
-        whenever(bubbleBarViewController.bubbleBarScaleY).thenReturn(barScaleY)
+        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.createRevealAnimatorForStashChange(any()))
+            .thenReturn(AnimatorSet())
     }
 
     private fun setUpBubbleStashedHandleViewController() {
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRule.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRule.kt
index bbcf566..cb5e464 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRule.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRule.kt
@@ -26,6 +26,7 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.rule.ServiceTestRule
 import com.android.launcher3.LauncherAppState
+import com.android.launcher3.statehandlers.DesktopVisibilityController
 import com.android.launcher3.taskbar.TaskbarActivityContext
 import com.android.launcher3.taskbar.TaskbarManager
 import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarNavButtonCallbacks
@@ -143,6 +144,7 @@
                                     PendingIntent(IIntentSender.Default())
                                 },
                                 object : TaskbarNavButtonCallbacks {},
+                                DesktopVisibilityController(context),
                             ) {
                             override fun recreateTaskbar() {
                                 super.recreateTaskbar()
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskGridNavHelperTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskGridNavHelperTest.java
deleted file mode 100644
index 7ef4910..0000000
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskGridNavHelperTest.java
+++ /dev/null
@@ -1,510 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quickstep.util;
-
-import static com.android.quickstep.util.TaskGridNavHelper.CLEAR_ALL_PLACEHOLDER_ID;
-import static com.android.quickstep.util.TaskGridNavHelper.INVALID_FOCUSED_TASK_ID;
-
-import static org.junit.Assert.assertEquals;
-
-import com.android.launcher3.util.IntArray;
-
-import org.junit.Test;
-
-public class TaskGridNavHelperTest {
-
-    @Test
-    public void equalLengthRows_noFocused_onTop_pressDown_goesToBottom() {
-        IntArray topIds = IntArray.wrap(1, 3, 5);
-        IntArray bottomIds = IntArray.wrap(2, 4, 6);
-        int currentPageTaskViewId = 1;
-        int delta = 1;
-        @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_DOWN;
-        boolean cycle = true;
-        TaskGridNavHelper taskGridNavHelper =
-                new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID);
-
-        int nextGridPage =
-                taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle);
-
-        assertEquals("Wrong next page returned.", 2, nextGridPage);
-    }
-
-    @Test
-    public void equalLengthRows_noFocused_onTop_pressUp_goesToBottom() {
-        IntArray topIds = IntArray.wrap(1, 3, 5);
-        IntArray bottomIds = IntArray.wrap(2, 4, 6);
-        int currentPageTaskViewId = 1;
-        int delta = 1;
-        @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_UP;
-        boolean cycle = true;
-        TaskGridNavHelper taskGridNavHelper =
-                new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID);
-
-        int nextGridPage =
-                taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle);
-
-        assertEquals("Wrong next page returned.", 2, nextGridPage);
-    }
-
-    @Test
-    public void equalLengthRows_noFocused_onBottom_pressDown_goesToTop() {
-        IntArray topIds = IntArray.wrap(1, 3, 5);
-        IntArray bottomIds = IntArray.wrap(2, 4, 6);
-        int currentPageTaskViewId = 2;
-        int delta = 1;
-        @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_DOWN;
-        boolean cycle = true;
-        TaskGridNavHelper taskGridNavHelper =
-                new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID);
-
-        int nextGridPage =
-                taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle);
-
-        assertEquals("Wrong next page returned.", 1, nextGridPage);
-    }
-
-    @Test
-    public void equalLengthRows_noFocused_onBottom_pressUp_goesToTop() {
-        IntArray topIds = IntArray.wrap(1, 3, 5);
-        IntArray bottomIds = IntArray.wrap(2, 4, 6);
-        int currentPageTaskViewId = 2;
-        int delta = 1;
-        @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_UP;
-        boolean cycle = true;
-        TaskGridNavHelper taskGridNavHelper =
-                new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID);
-
-        int nextGridPage =
-                taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle);
-
-        assertEquals("Wrong next page returned.", 1, nextGridPage);
-    }
-
-    @Test
-    public void equalLengthRows_noFocused_onTop_pressLeft_goesLeft() {
-        IntArray topIds = IntArray.wrap(1, 3, 5);
-        IntArray bottomIds = IntArray.wrap(2, 4, 6);
-        int currentPageTaskViewId = 1;
-        int delta = 1;
-        @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_LEFT;
-        boolean cycle = true;
-        TaskGridNavHelper taskGridNavHelper =
-                new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID);
-
-        int nextGridPage =
-                taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle);
-
-        assertEquals("Wrong next page returned.", 3, nextGridPage);
-    }
-
-    @Test
-    public void equalLengthRows_noFocused_onBottom_pressLeft_goesLeft() {
-        IntArray topIds = IntArray.wrap(1, 3, 5);
-        IntArray bottomIds = IntArray.wrap(2, 4, 6);
-        int currentPageTaskViewId = 2;
-        int delta = 1;
-        @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_LEFT;
-        boolean cycle = true;
-        TaskGridNavHelper taskGridNavHelper =
-                new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID);
-
-        int nextGridPage =
-                taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle);
-
-        assertEquals("Wrong next page returned.", 4, nextGridPage);
-    }
-
-    @Test
-    public void equalLengthRows_noFocused_onTop_secondItem_pressRight_goesRight() {
-        IntArray topIds = IntArray.wrap(1, 3, 5);
-        IntArray bottomIds = IntArray.wrap(2, 4, 6);
-        int currentPageTaskViewId = 3;
-        int delta = -1;
-        @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_RIGHT;
-        boolean cycle = true;
-        TaskGridNavHelper taskGridNavHelper =
-                new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID);
-
-        int nextGridPage =
-                taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle);
-
-        assertEquals("Wrong next page returned.", 1, nextGridPage);
-    }
-
-    @Test
-    public void equalLengthRows_noFocused_onBottom_secondItem_pressRight_goesRight() {
-        IntArray topIds = IntArray.wrap(1, 3, 5);
-        IntArray bottomIds = IntArray.wrap(2, 4, 6);
-        int currentPageTaskViewId = 4;
-        int delta = -1;
-        @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_RIGHT;
-        boolean cycle = true;
-        TaskGridNavHelper taskGridNavHelper =
-                new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID);
-
-        int nextGridPage =
-                taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle);
-
-        assertEquals("Wrong next page returned.", 2, nextGridPage);
-    }
-
-    @Test
-    public void equalLengthRows_noFocused_onTop_pressRight_cycleToClearAll() {
-        IntArray topIds = IntArray.wrap(1, 3, 5);
-        IntArray bottomIds = IntArray.wrap(2, 4, 6);
-        int currentPageTaskViewId = 1;
-        int delta = -1;
-        @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_RIGHT;
-        boolean cycle = true;
-        TaskGridNavHelper taskGridNavHelper =
-                new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID);
-
-        int nextGridPage =
-                taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle);
-
-        assertEquals("Wrong next page returned.", CLEAR_ALL_PLACEHOLDER_ID, nextGridPage);
-    }
-
-    @Test
-    public void equalLengthRows_noFocused_onBottom_pressRight_cycleToClearAll() {
-        IntArray topIds = IntArray.wrap(1, 3, 5);
-        IntArray bottomIds = IntArray.wrap(2, 4, 6);
-        int currentPageTaskViewId = 2;
-        int delta = -1;
-        @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_RIGHT;
-        boolean cycle = true;
-        TaskGridNavHelper taskGridNavHelper =
-                new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID);
-
-        int nextGridPage =
-                taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle);
-
-        assertEquals("Wrong next page returned.", CLEAR_ALL_PLACEHOLDER_ID, nextGridPage);
-    }
-
-    @Test
-    public void equalLengthRows_noFocused_onTop_lastItem_pressLeft_toClearAll() {
-        IntArray topIds = IntArray.wrap(1, 3, 5);
-        IntArray bottomIds = IntArray.wrap(2, 4, 6);
-        int currentPageTaskViewId = 5;
-        int delta = 1;
-        @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_LEFT;
-        boolean cycle = true;
-        TaskGridNavHelper taskGridNavHelper =
-                new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID);
-
-        int nextGridPage =
-                taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle);
-
-        assertEquals("Wrong next page returned.", CLEAR_ALL_PLACEHOLDER_ID, nextGridPage);
-    }
-
-    @Test
-    public void equalLengthRows_noFocused_onBottom_lastItem_pressLeft_toClearAll() {
-        IntArray topIds = IntArray.wrap(1, 3, 5);
-        IntArray bottomIds = IntArray.wrap(2, 4, 6);
-        int currentPageTaskViewId = 6;
-        int delta = 1;
-        @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_LEFT;
-        boolean cycle = true;
-        TaskGridNavHelper taskGridNavHelper =
-                new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID);
-
-        int nextGridPage =
-                taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle);
-
-        assertEquals("Wrong next page returned.", CLEAR_ALL_PLACEHOLDER_ID, nextGridPage);
-    }
-
-    @Test
-    public void equalLengthRows_noFocused_onClearAll_pressLeft_cycleToFirst() {
-        IntArray topIds = IntArray.wrap(1, 3, 5);
-        IntArray bottomIds = IntArray.wrap(2, 4, 6);
-        int currentPageTaskViewId = CLEAR_ALL_PLACEHOLDER_ID;
-        int delta = 1;
-        @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_LEFT;
-        boolean cycle = true;
-        TaskGridNavHelper taskGridNavHelper =
-                new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID);
-
-        int nextGridPage =
-                taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle);
-
-        assertEquals("Wrong next page returned.", 1, nextGridPage);
-    }
-
-    @Test
-    public void equalLengthRows_noFocused_onClearAll_pressRight_toLastInBottom() {
-        IntArray topIds = IntArray.wrap(1, 3, 5);
-        IntArray bottomIds = IntArray.wrap(2, 4, 6);
-        int currentPageTaskViewId = CLEAR_ALL_PLACEHOLDER_ID;
-        int delta = -1;
-        @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_RIGHT;
-        boolean cycle = true;
-        TaskGridNavHelper taskGridNavHelper =
-                new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID);
-
-        int nextGridPage =
-                taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle);
-
-        assertEquals("Wrong next page returned.", 6, nextGridPage);
-    }
-
-    @Test
-    public void equalLengthRows_withFocused_onFocused_pressLeft_toTop() {
-        IntArray topIds = IntArray.wrap(1, 3, 5);
-        IntArray bottomIds = IntArray.wrap(2, 4, 6);
-        int focusedTaskId = 99;
-        int currentPageTaskViewId = focusedTaskId;
-        int delta = 1;
-        @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_LEFT;
-        boolean cycle = true;
-        TaskGridNavHelper taskGridNavHelper =
-                new TaskGridNavHelper(topIds, bottomIds, focusedTaskId);
-
-        int nextGridPage =
-                taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle);
-
-        assertEquals("Wrong next page returned.", 1, nextGridPage);
-    }
-
-    @Test
-    public void equalLengthRows_withFocused_onFocused_pressUp_stayOnFocused() {
-        IntArray topIds = IntArray.wrap(1, 3, 5);
-        IntArray bottomIds = IntArray.wrap(2, 4, 6);
-        int focusedTaskId = 99;
-        int currentPageTaskViewId = focusedTaskId;
-        int delta = 1;
-        @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_UP;
-        boolean cycle = true;
-        TaskGridNavHelper taskGridNavHelper =
-                new TaskGridNavHelper(topIds, bottomIds, focusedTaskId);
-
-        int nextGridPage =
-                taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle);
-
-        assertEquals("Wrong next page returned.", focusedTaskId, nextGridPage);
-    }
-
-    @Test
-    public void equalLengthRows_withFocused_onFocused_pressDown_stayOnFocused() {
-        IntArray topIds = IntArray.wrap(1, 3, 5);
-        IntArray bottomIds = IntArray.wrap(2, 4, 6);
-        int focusedTaskId = 99;
-        int currentPageTaskViewId = focusedTaskId;
-        int delta = 1;
-        @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_DOWN;
-        boolean cycle = true;
-        TaskGridNavHelper taskGridNavHelper =
-                new TaskGridNavHelper(topIds, bottomIds, focusedTaskId);
-
-        int nextGridPage =
-                taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle);
-
-        assertEquals("Wrong next page returned.", focusedTaskId, nextGridPage);
-    }
-
-    @Test
-    public void equalLengthRows_withFocused_onFocused_pressRight_cycleToClearAll() {
-        IntArray topIds = IntArray.wrap(1, 3, 5);
-        IntArray bottomIds = IntArray.wrap(2, 4, 6);
-        int focusedTaskId = 99;
-        int currentPageTaskViewId = focusedTaskId;
-        int delta = -1;
-        @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_RIGHT;
-        boolean cycle = true;
-        TaskGridNavHelper taskGridNavHelper =
-                new TaskGridNavHelper(topIds, bottomIds, focusedTaskId);
-
-        int nextGridPage =
-                taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle);
-
-        assertEquals("Wrong next page returned.", CLEAR_ALL_PLACEHOLDER_ID, nextGridPage);
-    }
-
-    @Test
-    public void equalLengthRows_withFocused_onClearAll_pressLeft_cycleToFocusedTask() {
-        IntArray topIds = IntArray.wrap(1, 3, 5);
-        IntArray bottomIds = IntArray.wrap(2, 4, 6);
-        int focusedTaskId = 99;
-        int currentPageTaskViewId = CLEAR_ALL_PLACEHOLDER_ID;
-        int delta = 1;
-        @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_LEFT;
-        boolean cycle = true;
-        TaskGridNavHelper taskGridNavHelper =
-                new TaskGridNavHelper(topIds, bottomIds, focusedTaskId);
-
-        int nextGridPage =
-                taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle);
-
-        assertEquals("Wrong next page returned.", focusedTaskId, nextGridPage);
-    }
-
-    @Test
-    public void longerTopRow_noFocused_atEndTopBeyondBottom_pressDown_stayTop() {
-        IntArray topIds = IntArray.wrap(1, 3, 5, 7);
-        IntArray bottomIds = IntArray.wrap(2, 4, 6);
-        int currentPageTaskViewId = 7;
-        int delta = 1;
-        @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_DOWN;
-        boolean cycle = true;
-        TaskGridNavHelper taskGridNavHelper =
-                new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID);
-
-        int nextGridPage =
-                taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle);
-
-        assertEquals("Wrong next page returned.", 7, nextGridPage);
-    }
-
-    @Test
-    public void longerTopRow_noFocused_atEndTopBeyondBottom_pressUp_stayTop() {
-        IntArray topIds = IntArray.wrap(1, 3, 5, 7);
-        IntArray bottomIds = IntArray.wrap(2, 4, 6);
-        int currentPageTaskViewId = 7;
-        int delta = 1;
-        @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_UP;
-        boolean cycle = true;
-        TaskGridNavHelper taskGridNavHelper =
-                new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID);
-
-        int nextGridPage =
-                taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle);
-
-        assertEquals("Wrong next page returned.", 7, nextGridPage);
-    }
-
-    @Test
-    public void longerTopRow_noFocused_atEndBottom_pressLeft_goToTop() {
-        IntArray topIds = IntArray.wrap(1, 3, 5, 7);
-        IntArray bottomIds = IntArray.wrap(2, 4, 6);
-        int currentPageTaskViewId = 6;
-        int delta = 1;
-        @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_LEFT;
-        boolean cycle = true;
-        TaskGridNavHelper taskGridNavHelper =
-                new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID);
-
-        int nextGridPage =
-                taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle);
-
-        assertEquals("Wrong next page returned.", 7, nextGridPage);
-    }
-
-    @Test
-    public void longerTopRow_noFocused_atClearAll_pressRight_goToLonger() {
-        IntArray topIds = IntArray.wrap(1, 3, 5, 7);
-        IntArray bottomIds = IntArray.wrap(2, 4, 6);
-        int currentPageTaskViewId = CLEAR_ALL_PLACEHOLDER_ID;
-        int delta = -1;
-        @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_RIGHT;
-        boolean cycle = true;
-        TaskGridNavHelper taskGridNavHelper =
-                new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID);
-
-        int nextGridPage =
-                taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle);
-
-        assertEquals("Wrong next page returned.", 7, nextGridPage);
-    }
-
-    @Test
-    public void longerBottomRow_noFocused_atClearAll_pressRight_goToLonger() {
-        IntArray topIds = IntArray.wrap(1, 3, 5);
-        IntArray bottomIds = IntArray.wrap(2, 4, 6, 7);
-        int currentPageTaskViewId = CLEAR_ALL_PLACEHOLDER_ID;
-        int delta = -1;
-        @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_RIGHT;
-        boolean cycle = true;
-        TaskGridNavHelper taskGridNavHelper =
-                new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID);
-
-        int nextGridPage =
-                taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle);
-
-        assertEquals("Wrong next page returned.", 7, nextGridPage);
-    }
-
-    @Test
-    public void equalLengthRows_noFocused_onTop_pressTab_goesToBottom() {
-        IntArray topIds = IntArray.wrap(1, 3, 5);
-        IntArray bottomIds = IntArray.wrap(2, 4, 6);
-        int currentPageTaskViewId = 1;
-        int delta = 1;
-        @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_TAB;
-        boolean cycle = true;
-        TaskGridNavHelper taskGridNavHelper =
-                new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID);
-
-        int nextGridPage =
-                taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle);
-
-        assertEquals("Wrong next page returned.", 2, nextGridPage);
-    }
-
-    @Test
-    public void equalLengthRows_noFocused_onBottom_pressTab_goesToNextTop() {
-        IntArray topIds = IntArray.wrap(1, 3, 5);
-        IntArray bottomIds = IntArray.wrap(2, 4, 6);
-        int currentPageTaskViewId = 2;
-        int delta = 1;
-        @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_TAB;
-        boolean cycle = true;
-        TaskGridNavHelper taskGridNavHelper =
-                new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID);
-
-        int nextGridPage =
-                taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle);
-
-        assertEquals("Wrong next page returned.", 3, nextGridPage);
-    }
-
-    @Test
-    public void equalLengthRows_noFocused_onTop_pressTabWithShift_goesToPreviousBottom() {
-        IntArray topIds = IntArray.wrap(1, 3, 5);
-        IntArray bottomIds = IntArray.wrap(2, 4, 6);
-        int currentPageTaskViewId = 3;
-        int delta = -1;
-        @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_TAB;
-        boolean cycle = true;
-        TaskGridNavHelper taskGridNavHelper =
-                new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID);
-
-        int nextGridPage =
-                taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle);
-
-        assertEquals("Wrong next page returned.", 2, nextGridPage);
-    }
-
-    @Test
-    public void equalLengthRows_noFocused_onBottom_pressTabWithShift_goesToTop() {
-        IntArray topIds = IntArray.wrap(1, 3, 5);
-        IntArray bottomIds = IntArray.wrap(2, 4, 6);
-        int currentPageTaskViewId = 2;
-        int delta = -1;
-        @TaskGridNavHelper.TASK_NAV_DIRECTION int direction = TaskGridNavHelper.DIRECTION_TAB;
-        boolean cycle = true;
-        TaskGridNavHelper taskGridNavHelper =
-                new TaskGridNavHelper(topIds, bottomIds, INVALID_FOCUSED_TASK_ID);
-
-        int nextGridPage =
-                taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle);
-
-        assertEquals("Wrong next page returned.", 1, nextGridPage);
-    }
-}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskGridNavHelperTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskGridNavHelperTest.kt
new file mode 100644
index 0000000..7aab75f
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskGridNavHelperTest.kt
@@ -0,0 +1,638 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.util
+
+import com.android.launcher3.util.IntArray
+import com.android.quickstep.util.TaskGridNavHelper.CLEAR_ALL_PLACEHOLDER_ID
+import com.android.quickstep.util.TaskGridNavHelper.DIRECTION_DOWN
+import com.android.quickstep.util.TaskGridNavHelper.DIRECTION_LEFT
+import com.android.quickstep.util.TaskGridNavHelper.DIRECTION_RIGHT
+import com.android.quickstep.util.TaskGridNavHelper.DIRECTION_TAB
+import com.android.quickstep.util.TaskGridNavHelper.DIRECTION_UP
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+class TaskGridNavHelperTest {
+
+    /*
+                    5   3   1
+        CLEAR_ALL           ↓
+                    6   4   2
+    */
+    @Test
+    fun equalLengthRows_noFocused_onTop_pressDown_goesToBottom() {
+        assertThat(getNextGridPage(currentPageTaskViewId = 1, DIRECTION_DOWN, delta = 1))
+            .isEqualTo(2)
+    }
+
+    /*                      ↑----→
+                    5   3   1    |
+        CLEAR_ALL                |
+                    6   4   2←---|
+    */
+    @Test
+    fun equalLengthRows_noFocused_onTop_pressUp_goesToBottom() {
+        assertThat(getNextGridPage(currentPageTaskViewId = 1, DIRECTION_UP, delta = 1)).isEqualTo(2)
+    }
+
+    /*                      ↓----↑
+                    5   3   1    |
+        CLEAR_ALL                |
+                    6   4   2    |
+                            ↓----→
+    */
+    @Test
+    fun equalLengthRows_noFocused_onBottom_pressDown_goesToTop() {
+        assertThat(getNextGridPage(currentPageTaskViewId = 2, DIRECTION_DOWN, delta = 1))
+            .isEqualTo(1)
+    }
+
+    /*
+                    5   3   1
+        CLEAR_ALL           ↑
+                    6   4   2
+    */
+    @Test
+    fun equalLengthRows_noFocused_onBottom_pressUp_goesToTop() {
+        assertThat(getNextGridPage(currentPageTaskViewId = 2, DIRECTION_UP, delta = 1)).isEqualTo(1)
+    }
+
+    /*
+                    5   3<--1
+        CLEAR_ALL
+                    6   4   2
+    */
+    @Test
+    fun equalLengthRows_noFocused_onTop_pressLeft_goesLeft() {
+        assertThat(getNextGridPage(currentPageTaskViewId = 1, DIRECTION_LEFT, delta = 1))
+            .isEqualTo(3)
+    }
+
+    /*
+                    5   3   1
+        CLEAR_ALL
+                    6   4<--2
+    */
+    @Test
+    fun equalLengthRows_noFocused_onBottom_pressLeft_goesLeft() {
+        assertThat(getNextGridPage(currentPageTaskViewId = 2, DIRECTION_LEFT, delta = 1))
+            .isEqualTo(4)
+    }
+
+    /*
+                    5   3-->1
+        CLEAR_ALL
+                    6   4   2
+    */
+    @Test
+    fun equalLengthRows_noFocused_onTop_secondItem_pressRight_goesRight() {
+        assertThat(getNextGridPage(currentPageTaskViewId = 3, DIRECTION_RIGHT, delta = -1))
+            .isEqualTo(1)
+    }
+
+    /*
+                    5   3   1
+        CLEAR_ALL
+                    6   4-->2
+    */
+    @Test
+    fun equalLengthRows_noFocused_onBottom_secondItem_pressRight_goesRight() {
+        assertThat(getNextGridPage(currentPageTaskViewId = 4, DIRECTION_RIGHT, delta = -1))
+            .isEqualTo(2)
+    }
+
+    /*
+             ↓------------------←
+             |                  |
+             ↓      5   3   1---→
+        CLEAR_ALL
+                    6   4   2
+    */
+    @Test
+    fun equalLengthRows_noFocused_onTop_pressRight_cycleToClearAll() {
+        assertThat(getNextGridPage(currentPageTaskViewId = 1, DIRECTION_RIGHT, delta = -1))
+            .isEqualTo(CLEAR_ALL_PLACEHOLDER_ID)
+    }
+
+    /*
+             ↓------------------←
+             |                  ↑
+             ↓      5   3   1   |
+        CLEAR_ALL               ↑
+                    6   4   2---→
+    */
+    @Test
+    fun equalLengthRows_noFocused_onBottom_pressRight_cycleToClearAll() {
+        assertThat(getNextGridPage(currentPageTaskViewId = 2, DIRECTION_RIGHT, delta = -1))
+            .isEqualTo(CLEAR_ALL_PLACEHOLDER_ID)
+    }
+
+    /*
+              ←----5   3   1
+              ↓
+        CLEAR_ALL
+                   6   4   2
+    */
+    @Test
+    fun equalLengthRows_noFocused_onTop_lastItem_pressLeft_toClearAll() {
+        assertThat(getNextGridPage(currentPageTaskViewId = 5, DIRECTION_LEFT, delta = 1))
+            .isEqualTo(CLEAR_ALL_PLACEHOLDER_ID)
+    }
+
+    /*
+                   5   3   1
+        CLEAR_ALL
+               ↑
+               ←---6   4   2
+    */
+    @Test
+    fun equalLengthRows_noFocused_onBottom_lastItem_pressLeft_toClearAll() {
+        assertThat(getNextGridPage(currentPageTaskViewId = 6, DIRECTION_LEFT, delta = 1))
+            .isEqualTo(CLEAR_ALL_PLACEHOLDER_ID)
+    }
+
+    /*
+       |→-----------------------|
+       |                        ↓
+       ↑                5   3   1
+       ←------CLEAR_ALL
+
+                        6   4   2
+    */
+    @Test
+    fun equalLengthRows_noFocused_onClearAll_pressLeft_cycleToFirst() {
+        assertThat(
+                getNextGridPage(
+                    currentPageTaskViewId = CLEAR_ALL_PLACEHOLDER_ID,
+                    DIRECTION_LEFT,
+                    delta = 1,
+                )
+            )
+            .isEqualTo(1)
+    }
+
+    /*
+                       5   3   1
+        CLEAR_ALL--↓
+                   |
+                   |--→6   4   2
+    */
+    @Test
+    fun equalLengthRows_noFocused_onClearAll_pressRight_toLastInBottom() {
+        assertThat(
+                getNextGridPage(
+                    currentPageTaskViewId = CLEAR_ALL_PLACEHOLDER_ID,
+                    DIRECTION_RIGHT,
+                    delta = -1,
+                )
+            )
+            .isEqualTo(6)
+    }
+
+    /*
+                    5   3   1←---
+                                 ↑
+        CLEAR_ALL                ←--FOCUSED_TASK
+                    6   4   2
+    */
+    @Test
+    fun equalLengthRows_withFocused_onFocused_pressLeft_toTop() {
+        assertThat(
+                getNextGridPage(
+                    currentPageTaskViewId = FOCUSED_TASK_ID,
+                    DIRECTION_LEFT,
+                    delta = 1,
+                    largeTileIds = listOf(FOCUSED_TASK_ID),
+                )
+            )
+            .isEqualTo(1)
+    }
+
+    /*
+                        5   3   1
+                                   ←--↑
+        CLEAR_ALL                  ↓-→FOCUSED_TASK
+                        6   4   2
+    */
+    @Test
+    fun equalLengthRows_withFocused_onFocused_pressUp_stayOnFocused() {
+        assertThat(
+                getNextGridPage(
+                    currentPageTaskViewId = FOCUSED_TASK_ID,
+                    DIRECTION_UP,
+                    delta = 1,
+                    largeTileIds = listOf(FOCUSED_TASK_ID),
+                )
+            )
+            .isEqualTo(FOCUSED_TASK_ID)
+    }
+
+    /*
+                        5   3   1
+        CLEAR_ALL                  ↑--→FOCUSED_TASK
+                                   ↑←--↓
+                        6   4   2
+    */
+
+    @Test
+    fun equalLengthRows_withFocused_onFocused_pressDown_stayOnFocused() {
+
+        assertThat(
+                getNextGridPage(
+                    currentPageTaskViewId = FOCUSED_TASK_ID,
+                    DIRECTION_DOWN,
+                    delta = 1,
+                    largeTileIds = listOf(FOCUSED_TASK_ID),
+                )
+            )
+            .isEqualTo(FOCUSED_TASK_ID)
+    }
+
+    /*
+             ↓-------------------------------←|
+             |                                ↑
+             ↓      5   3   1                 |
+        CLEAR_ALL               FOCUSED_TASK--→
+                    6   4   2
+    */
+    @Test
+    fun equalLengthRows_withFocused_onFocused_pressRight_cycleToClearAll() {
+
+        assertThat(
+                getNextGridPage(
+                    currentPageTaskViewId = FOCUSED_TASK_ID,
+                    DIRECTION_RIGHT,
+                    delta = -1,
+                    largeTileIds = listOf(FOCUSED_TASK_ID),
+                )
+            )
+            .isEqualTo(CLEAR_ALL_PLACEHOLDER_ID)
+    }
+
+    /*
+           |→---------------------------|
+           |                            |
+           ↑                5   3   1   ↓
+           ←------CLEAR_ALL            FOCUSED_TASK
+
+                            6   4   2
+    */
+    @Test
+    fun equalLengthRows_withFocused_onClearAll_pressLeft_cycleToFocusedTask() {
+
+        assertThat(
+                getNextGridPage(
+                    currentPageTaskViewId = CLEAR_ALL_PLACEHOLDER_ID,
+                    DIRECTION_LEFT,
+                    delta = 1,
+                    largeTileIds = listOf(FOCUSED_TASK_ID),
+                )
+            )
+            .isEqualTo(FOCUSED_TASK_ID)
+    }
+
+    /*
+                         7←-↑  5   3   1
+                         ↓--→
+           CLEAR_ALL
+                               6   4   2
+    */
+    @Test
+    fun longerTopRow_noFocused_atEndTopBeyondBottom_pressDown_stayTop() {
+        assertThat(
+                getNextGridPage(
+                    currentPageTaskViewId = 7,
+                    DIRECTION_DOWN,
+                    delta = 1,
+                    topIds = IntArray.wrap(1, 3, 5, 7),
+                )
+            )
+            .isEqualTo(7)
+    }
+
+    /*
+                       ←--↑
+                       ↓-→7   5   3   1
+           CLEAR_ALL
+                              6   4   2
+    */
+    @Test
+    fun longerTopRow_noFocused_atEndTopBeyondBottom_pressUp_stayTop() {
+        assertThat(
+                getNextGridPage(
+                    /* topIds = */ currentPageTaskViewId = 7,
+                    DIRECTION_UP,
+                    delta = 1,
+                    topIds = IntArray.wrap(1, 3, 5, 7),
+                )
+            )
+            .isEqualTo(7)
+    }
+
+    /*
+                         7   5   3   1
+           CLEAR_ALL     ↑
+                         ←----6   4   2
+    */
+    @Test
+    fun longerTopRow_noFocused_atEndBottom_pressLeft_goToTop() {
+        assertThat(
+                getNextGridPage(
+                    /* topIds = */ currentPageTaskViewId = 6,
+                    DIRECTION_LEFT,
+                    delta = 1,
+                    topIds = IntArray.wrap(1, 3, 5, 7),
+                )
+            )
+            .isEqualTo(7)
+    }
+
+    /*
+                         7   5   3   1
+                         ↑
+           CLEAR_ALL-----→
+                             6   4   2
+    */
+    @Test
+    fun longerTopRow_noFocused_atClearAll_pressRight_goToLonger() {
+        assertThat(
+                getNextGridPage(
+                    /* topIds = */ currentPageTaskViewId = CLEAR_ALL_PLACEHOLDER_ID,
+                    DIRECTION_RIGHT,
+                    delta = -1,
+                    topIds = IntArray.wrap(1, 3, 5, 7),
+                )
+            )
+            .isEqualTo(7)
+    }
+
+    /*
+                            5   3   1
+           CLEAR_ALL-----→
+                         ↓
+                         7  6   4   2
+    */
+    @Test
+    fun longerBottomRow_noFocused_atClearAll_pressRight_goToLonger() {
+        assertThat(
+                getNextGridPage(
+                    currentPageTaskViewId = CLEAR_ALL_PLACEHOLDER_ID,
+                    DIRECTION_RIGHT,
+                    delta = -1,
+                    bottomIds = IntArray.wrap(2, 4, 6, 7),
+                )
+            )
+            .isEqualTo(7)
+    }
+
+    /*
+                   5   3   1
+        CLEAR_ALL          ↓
+                   6   4   2
+    */
+    @Test
+    fun equalLengthRows_noFocused_onTop_pressTab_goesToBottom() {
+        assertThat(getNextGridPage(currentPageTaskViewId = 1, DIRECTION_TAB, delta = 1))
+            .isEqualTo(2)
+    }
+
+    /*
+                   5   3   1
+        CLEAR_ALL      ↑
+                       ←---↑
+                   6   4   2
+    */
+    @Test
+    fun equalLengthRows_noFocused_onBottom_pressTab_goesToNextTop() {
+        assertThat(getNextGridPage(currentPageTaskViewId = 2, DIRECTION_TAB, delta = 1))
+            .isEqualTo(3)
+    }
+
+    /*
+                   5   3   1
+        CLEAR_ALL      ↓
+                       ----→
+                           ↓
+                   6   4   2
+    */
+    @Test
+    fun equalLengthRows_noFocused_onTop_pressTabWithShift_goesToPreviousBottom() {
+        assertThat(getNextGridPage(currentPageTaskViewId = 3, DIRECTION_TAB, delta = -1))
+            .isEqualTo(2)
+    }
+
+    /*
+                   5   3   1
+        CLEAR_ALL          ↑
+                   6   4   2
+    */
+    @Test
+    fun equalLengthRows_noFocused_onBottom_pressTabWithShift_goesToTop() {
+        assertThat(getNextGridPage(currentPageTaskViewId = 2, DIRECTION_TAB, delta = -1))
+            .isEqualTo(1)
+    }
+
+    /*
+                        5   3   1
+           CLEAR_ALL                FOCUSED_TASK←--DESKTOP
+                        6   4   2
+    */
+    @Test
+    fun withLargeTile_pressLeftFromDesktopTask_goesToFocusedTask() {
+        assertThat(
+                getNextGridPage(
+                    currentPageTaskViewId = DESKTOP_TASK_ID,
+                    DIRECTION_LEFT,
+                    delta = 1,
+                    largeTileIds = listOf(DESKTOP_TASK_ID, FOCUSED_TASK_ID),
+                )
+            )
+            .isEqualTo(FOCUSED_TASK_ID)
+    }
+
+    /*
+                        5   3   1
+           CLEAR_ALL                FOCUSED_TASK--→DESKTOP
+                        6   4   2
+    */
+    @Test
+    fun withLargeTile_pressRightFromFocusedTask_goesToDesktopTask() {
+        assertThat(
+                getNextGridPage(
+                    currentPageTaskViewId = FOCUSED_TASK_ID,
+                    DIRECTION_RIGHT,
+                    delta = -1,
+                    largeTileIds = listOf(DESKTOP_TASK_ID, FOCUSED_TASK_ID),
+                )
+            )
+            .isEqualTo(DESKTOP_TASK_ID)
+    }
+
+    /*
+             ↓-----------------------------------------←|
+             |                                          |
+             ↓      5   3   1                           ↑
+        CLEAR_ALL               FOCUSED_TASK   DESKTOP--→
+                    6   4   2
+    */
+    @Test
+    fun withLargeTile_pressRightFromDesktopTask_goesToClearAll() {
+        assertThat(
+                getNextGridPage(
+                    currentPageTaskViewId = DESKTOP_TASK_ID,
+                    DIRECTION_RIGHT,
+                    delta = -1,
+                    largeTileIds = listOf(DESKTOP_TASK_ID, FOCUSED_TASK_ID),
+                )
+            )
+            .isEqualTo(CLEAR_ALL_PLACEHOLDER_ID)
+    }
+
+    /*
+           |→-------------------------------------------|
+           |                                            |
+           ↑                5   3   1                   ↓
+           ←------CLEAR_ALL             FOCUSED_TASK   DESKTOP
+
+                            6   4   2
+    */
+    @Test
+    fun withLargeTile_pressLeftFromClearAll_goesToDesktopTask() {
+        assertThat(
+                getNextGridPage(
+                    currentPageTaskViewId = CLEAR_ALL_PLACEHOLDER_ID,
+                    DIRECTION_LEFT,
+                    delta = 1,
+                    largeTileIds = listOf(DESKTOP_TASK_ID, FOCUSED_TASK_ID),
+                )
+            )
+            .isEqualTo(DESKTOP_TASK_ID)
+    }
+
+    /*
+                    5   3   1
+       CLEAR_ALL                 FOCUSED_TASK   DESKTOP
+                                  ↑
+                    6   4   2→----↑
+    */
+    @Test
+    fun withLargeTile_pressRightFromBottom_goesToLargeTile() {
+        assertThat(
+                getNextGridPage(
+                    currentPageTaskViewId = 2,
+                    DIRECTION_RIGHT,
+                    delta = -1,
+                    largeTileIds = listOf(DESKTOP_TASK_ID, FOCUSED_TASK_ID),
+                )
+            )
+            .isEqualTo(FOCUSED_TASK_ID)
+    }
+
+    /*
+                        5   3   1→----|
+                                      ↓
+         CLEAR_ALL                    FOCUSED_TASK   DESKTOP
+                        6   4   2
+    */
+    @Test
+    fun withLargeTile_pressRightFromTop_goesToLargeTile() {
+        assertThat(
+                getNextGridPage(
+                    currentPageTaskViewId = 1,
+                    DIRECTION_RIGHT,
+                    delta = -1,
+                    largeTileIds = listOf(DESKTOP_TASK_ID, FOCUSED_TASK_ID),
+                )
+            )
+            .isEqualTo(FOCUSED_TASK_ID)
+    }
+
+    /*
+                        5   3   1
+
+         CLEAR_ALL                FOCUSED_TASK←---DESKTOP
+                        6   4   2
+    */
+    @Test
+    fun withLargeTile_pressTabFromDeskTop_goesToFocusedTask() {
+        assertThat(
+                getNextGridPage(
+                    currentPageTaskViewId = DESKTOP_TASK_ID,
+                    DIRECTION_TAB,
+                    delta = 1,
+                    largeTileIds = listOf(DESKTOP_TASK_ID, FOCUSED_TASK_ID),
+                )
+            )
+            .isEqualTo(FOCUSED_TASK_ID)
+    }
+
+    /*
+        CLEAR_ALL         FOCUSED_TASK   DESKTOP
+                           ↓
+                     2←----↓
+    */
+    @Test
+    fun withLargeTile_pressLeftFromLargeTile_goesToBottom() {
+        assertThat(
+                getNextGridPage(
+                    currentPageTaskViewId = FOCUSED_TASK_ID,
+                    DIRECTION_LEFT,
+                    delta = 1,
+                    topIds = IntArray(),
+                    bottomIds = IntArray.wrap(2),
+                    largeTileIds = listOf(DESKTOP_TASK_ID, FOCUSED_TASK_ID),
+                )
+            )
+            .isEqualTo(2)
+    }
+
+    /*
+             ↓-----------------------------------------←|
+             |                                          |
+             ↓      5   3   1                           ↑
+        CLEAR_ALL               FOCUSED_TASK   DESKTOP--→
+                    6   4   2
+    */
+    @Test
+    fun withLargeTile_pressShiftTabFromDeskTop_goesToClearAll() {
+        assertThat(
+                getNextGridPage(
+                    currentPageTaskViewId = DESKTOP_TASK_ID,
+                    DIRECTION_TAB,
+                    delta = -1,
+                    largeTileIds = listOf(DESKTOP_TASK_ID, FOCUSED_TASK_ID),
+                )
+            )
+            .isEqualTo(CLEAR_ALL_PLACEHOLDER_ID)
+    }
+
+    private fun getNextGridPage(
+        currentPageTaskViewId: Int,
+        direction: Int,
+        delta: Int,
+        topIds: IntArray = IntArray.wrap(1, 3, 5),
+        bottomIds: IntArray = IntArray.wrap(2, 4, 6),
+        largeTileIds: List<Int> = emptyList(),
+    ): Int {
+        val taskGridNavHelper = TaskGridNavHelper(topIds, bottomIds, largeTileIds)
+        return taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, true)
+    }
+
+    private companion object {
+        const val FOCUSED_TASK_ID = 99
+        const val DESKTOP_TASK_ID = 100
+    }
+}
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
index 88ffeea..b67bc5a 100644
--- a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
+++ b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
@@ -31,7 +31,6 @@
 import com.android.launcher3.model.data.AppInfo
 import com.android.launcher3.model.data.ItemInfo
 import com.android.launcher3.model.data.TaskItemInfo
-import com.android.launcher3.statehandlers.DesktopVisibilityController
 import com.android.quickstep.RecentsModel
 import com.android.quickstep.RecentsModel.RecentTasksChangedListener
 import com.android.quickstep.TaskIconCache
@@ -66,9 +65,7 @@
                 // Update canShowRunningAndRecentAppsAtInit before setUp() is called for each test.
                 canShowRunningAndRecentAppsAtInit =
                     description.methodName !in
-                        listOf(
-                            "canShowRunningAndRecentAppsAtInitIsFalse_getTasksNeverCalled",
-                        )
+                        listOf("canShowRunningAndRecentAppsAtInitIsFalse_getTasksNeverCalled")
             }
         }
 
@@ -76,7 +73,6 @@
     @Mock private lateinit var mockRecentsModel: RecentsModel
     @Mock private lateinit var mockContext: Context
     @Mock private lateinit var mockResources: Resources
-    @Mock private lateinit var mockDesktopVisibilityController: DesktopVisibilityController
 
     private var taskListChangeId: Int = 1
 
@@ -100,13 +96,11 @@
             recentTasksChangedListener = null
             it
         }
-        recentAppsController =
-            TaskbarRecentAppsController(mockContext, mockRecentsModel) {
-                mockDesktopVisibilityController
-            }
+        recentAppsController = TaskbarRecentAppsController(mockContext, mockRecentsModel)
         recentAppsController.canShowRunningApps = canShowRunningAndRecentAppsAtInit
         recentAppsController.canShowRecentApps = canShowRunningAndRecentAppsAtInit
         recentAppsController.init(taskbarControllers)
+        taskbarControllers.onPostInit()
 
         recentTasksChangedListener =
             if (canShowRunningAndRecentAppsAtInit) {
@@ -133,7 +127,7 @@
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2),
             runningTasks = listOf(createTask(1, RUNNING_APP_PACKAGE_1)),
-            recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2)
+            recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2),
         )
         verify(mockRecentsModel, never()).getTasks(any<Consumer<List<GroupTask>>>())
     }
@@ -147,7 +141,7 @@
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2),
             runningTasks = listOf(createTask(1, RUNNING_APP_PACKAGE_1)),
-            recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2)
+            recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2),
         )
         // Verify that getTasks() was not called again after the init().
         verify(mockRecentsModel, times(1)).getTasks(any<Consumer<List<GroupTask>>>())
@@ -162,7 +156,7 @@
             prepareHotseatAndRunningAndRecentApps(
                 hotseatPackages = hotseatPackages,
                 runningTasks = emptyList(),
-                recentTaskPackages = emptyList()
+                recentTaskPackages = emptyList(),
             )
         assertThat(newHotseatItems.map { it?.targetPackage })
             .containsExactlyElementsIn(hotseatPackages)
@@ -177,7 +171,7 @@
             prepareHotseatAndRunningAndRecentApps(
                 hotseatPackages = hotseatPackages,
                 runningTasks = emptyList(),
-                recentTaskPackages = emptyList()
+                recentTaskPackages = emptyList(),
             )
         assertThat(newHotseatItems.map { it?.targetPackage })
             .containsExactlyElementsIn(hotseatPackages)
@@ -191,7 +185,7 @@
             prepareHotseatAndRunningAndRecentApps(
                 hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2, PREDICTED_PACKAGE_1),
                 runningTasks = emptyList(),
-                recentTaskPackages = emptyList()
+                recentTaskPackages = emptyList(),
             )
         val expectedPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2)
         assertThat(newHotseatItems.map { it?.targetPackage })
@@ -206,7 +200,7 @@
             prepareHotseatAndRunningAndRecentApps(
                 hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2),
                 runningTasks = listOf(createTask(id = 1, HOTSEAT_PACKAGE_1)),
-                recentTaskPackages = emptyList()
+                recentTaskPackages = emptyList(),
             )
 
         assertThat(newHotseatItems).hasLength(2)
@@ -226,9 +220,9 @@
                 runningTasks =
                     listOf(
                         createTask(id = 1, HOTSEAT_PACKAGE_1),
-                        createTask(id = 2, HOTSEAT_PACKAGE_1)
+                        createTask(id = 2, HOTSEAT_PACKAGE_1),
                     ),
-                recentTaskPackages = emptyList()
+                recentTaskPackages = emptyList(),
             )
 
         // First task is in Hotseat Items
@@ -251,7 +245,7 @@
             prepareHotseatAndRunningAndRecentApps(
                 hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2, PREDICTED_PACKAGE_1),
                 runningTasks = emptyList(),
-                recentTaskPackages = emptyList()
+                recentTaskPackages = emptyList(),
             )
         val expectedPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2)
         assertThat(newHotseatItems.map { it?.targetPackage })
@@ -267,9 +261,9 @@
             runningTasks =
                 listOf(
                     createTask(id = 1, RUNNING_APP_PACKAGE_1),
-                    createTask(id = 2, RUNNING_APP_PACKAGE_2)
+                    createTask(id = 2, RUNNING_APP_PACKAGE_2),
                 ),
-            recentTaskPackages = emptyList()
+            recentTaskPackages = emptyList(),
         )
         assertThat(recentAppsController.shownTasks).isEmpty()
     }
@@ -281,7 +275,7 @@
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2, PREDICTED_PACKAGE_1),
             runningTasks = emptyList(),
-            recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2)
+            recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2),
         )
         assertThat(recentAppsController.shownTasks).isEmpty()
     }
@@ -294,9 +288,9 @@
             runningTasks =
                 listOf(
                     createTask(id = 1, RUNNING_APP_PACKAGE_1),
-                    createTask(id = 2, RUNNING_APP_PACKAGE_2)
+                    createTask(id = 2, RUNNING_APP_PACKAGE_2),
                 ),
-            recentTaskPackages = emptyList()
+            recentTaskPackages = emptyList(),
         )
         assertThat(recentAppsController.shownTasks).isEmpty()
         assertThat(recentAppsController.minimizedTaskIds).isEmpty()
@@ -308,7 +302,7 @@
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
             runningTasks = emptyList(),
-            recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2)
+            recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2),
         )
         assertThat(recentAppsController.shownTasks).isEmpty()
     }
@@ -321,7 +315,7 @@
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
             runningTasks = listOf(task1, task2),
-            recentTaskPackages = emptyList()
+            recentTaskPackages = emptyList(),
         )
         val shownTasks = recentAppsController.shownTasks.map { it.task1 }
         assertThat(shownTasks).containsExactlyElementsIn(listOf(task1, task2))
@@ -335,7 +329,7 @@
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
             runningTasks = listOf(task1, task2),
-            recentTaskPackages = emptyList()
+            recentTaskPackages = emptyList(),
         )
         assertThat(recentAppsController.runningTaskIds).isEmpty()
         assertThat(recentAppsController.minimizedTaskIds).isEmpty()
@@ -349,7 +343,7 @@
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
             runningTasks = listOf(task1, task2),
-            recentTaskPackages = emptyList()
+            recentTaskPackages = emptyList(),
         )
         assertThat(recentAppsController.runningTaskIds).containsExactlyElementsIn(listOf(1, 2))
         assertThat(recentAppsController.minimizedTaskIds).isEmpty()
@@ -362,12 +356,12 @@
             listOf(
                 createTask(id = 1, HOTSEAT_PACKAGE_1),
                 createTask(id = 2, RUNNING_APP_PACKAGE_1),
-                createTask(id = 3, RUNNING_APP_PACKAGE_2)
+                createTask(id = 3, RUNNING_APP_PACKAGE_2),
             )
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2),
             runningTasks = runningTasks,
-            recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2)
+            recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2),
         )
         assertThat(recentAppsController.runningTaskIds).containsExactlyElementsIn(listOf(1, 2, 3))
         assertThat(recentAppsController.minimizedTaskIds).isEmpty()
@@ -383,7 +377,7 @@
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
             runningTasks = runningTasks,
-            recentTaskPackages = emptyList()
+            recentTaskPackages = emptyList(),
         )
         assertThat(recentAppsController.runningTaskIds).containsExactly(1, 2, 3)
         assertThat(recentAppsController.minimizedTaskIds).containsExactly(3)
@@ -397,7 +391,7 @@
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
             runningTasks = listOf(task1, task2),
-            recentTaskPackages = emptyList()
+            recentTaskPackages = emptyList(),
         )
         assertThat(recentAppsController.runningTaskIds).containsExactlyElementsIn(listOf(1, 2))
     }
@@ -410,13 +404,13 @@
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
             runningTasks = listOf(task1, task2),
-            recentTaskPackages = emptyList()
+            recentTaskPackages = emptyList(),
         )
 
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
             runningTasks = listOf(task2, task1),
-            recentTaskPackages = emptyList()
+            recentTaskPackages = emptyList(),
         )
 
         val shownTasks = recentAppsController.shownTasks.map { it.task1 }
@@ -431,13 +425,13 @@
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
             runningTasks = listOf(task1, task2),
-            recentTaskPackages = emptyList()
+            recentTaskPackages = emptyList(),
         )
 
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
             runningTasks = listOf(task2, task1),
-            recentTaskPackages = emptyList()
+            recentTaskPackages = emptyList(),
         )
 
         val shownTasks = recentAppsController.shownTasks.map { it.task1 }
@@ -452,17 +446,17 @@
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = listOf(RUNNING_APP_PACKAGE_1),
             runningTasks = listOf(task1, task2),
-            recentTaskPackages = emptyList()
+            recentTaskPackages = emptyList(),
         )
         updateRecentTasks( // Trigger a recent-tasks change before calling updateHotseatItems()
             runningTasks = listOf(task2, task1),
-            recentTaskPackages = emptyList()
+            recentTaskPackages = emptyList(),
         )
 
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = listOf(RUNNING_APP_PACKAGE_1),
             runningTasks = listOf(task2, task1),
-            recentTaskPackages = emptyList()
+            recentTaskPackages = emptyList(),
         )
 
         val newHotseatItems = recentAppsController.shownHotseatItems
@@ -479,12 +473,12 @@
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
             runningTasks = emptyList(),
-            recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2, RECENT_PACKAGE_3)
+            recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2, RECENT_PACKAGE_3),
         )
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
             runningTasks = emptyList(),
-            recentTaskPackages = listOf(RECENT_PACKAGE_2, RECENT_PACKAGE_3, RECENT_PACKAGE_1)
+            recentTaskPackages = listOf(RECENT_PACKAGE_2, RECENT_PACKAGE_3, RECENT_PACKAGE_1),
         )
         val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames }
         // Most recent packages, minus the currently running one (RECENT_PACKAGE_1).
@@ -500,12 +494,12 @@
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
             runningTasks = listOf(task1, task2),
-            recentTaskPackages = emptyList()
+            recentTaskPackages = emptyList(),
         )
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
             runningTasks = listOf(task2, task1, task3),
-            recentTaskPackages = emptyList()
+            recentTaskPackages = emptyList(),
         )
         val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames }
         val expectedOrder =
@@ -519,12 +513,12 @@
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
             runningTasks = emptyList(),
-            recentTaskPackages = listOf(RECENT_PACKAGE_3, RECENT_PACKAGE_2)
+            recentTaskPackages = listOf(RECENT_PACKAGE_3, RECENT_PACKAGE_2),
         )
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
             runningTasks = emptyList(),
-            recentTaskPackages = listOf(RECENT_PACKAGE_2, RECENT_PACKAGE_3, RECENT_PACKAGE_1)
+            recentTaskPackages = listOf(RECENT_PACKAGE_2, RECENT_PACKAGE_3, RECENT_PACKAGE_1),
         )
         val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames }
         // Most recent packages, minus the currently running one (RECENT_PACKAGE_1).
@@ -540,12 +534,12 @@
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
             runningTasks = listOf(task1, task2, task3),
-            recentTaskPackages = emptyList()
+            recentTaskPackages = emptyList(),
         )
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
             runningTasks = listOf(task2, task1),
-            recentTaskPackages = emptyList()
+            recentTaskPackages = emptyList(),
         )
         val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames }
         assertThat(shownPackages).isEqualTo(listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2))
@@ -557,12 +551,12 @@
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
             runningTasks = emptyList(),
-            recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2, RECENT_PACKAGE_3)
+            recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2, RECENT_PACKAGE_3),
         )
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
             runningTasks = emptyList(),
-            recentTaskPackages = listOf(RECENT_PACKAGE_2, RECENT_PACKAGE_3)
+            recentTaskPackages = listOf(RECENT_PACKAGE_2, RECENT_PACKAGE_3),
         )
         val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames }
         // Most recent packages, minus the currently running one (RECENT_PACKAGE_3).
@@ -579,7 +573,7 @@
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
             runningTasks = listOf(runningTask1, runningTask2),
-            recentTaskPackages = recentTaskPackages
+            recentTaskPackages = recentTaskPackages,
         )
 
         setInDesktopMode(true)
@@ -597,7 +591,7 @@
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
             runningTasks = listOf(runningTask1, runningTask2),
-            recentTaskPackages = recentTaskPackages
+            recentTaskPackages = recentTaskPackages,
         )
         setInDesktopMode(false)
         recentTasksChangedListener!!.onRecentTasksChanged()
@@ -613,7 +607,7 @@
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
             runningTasks = emptyList(),
-            recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2, RECENT_PACKAGE_3)
+            recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2, RECENT_PACKAGE_3),
         )
         val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames }
         // RECENT_PACKAGE_3 is the top task (visible to user) so should be excluded.
@@ -629,7 +623,7 @@
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
             runningTasks = listOf(runningTask1, runningTask2),
-            recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2)
+            recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2),
         )
         val shownPackages = recentAppsController.shownTasks.map { it.packageNames }
         // Only 2 recent tasks shown: Desktop Tile + 1 Recent Task
@@ -645,7 +639,7 @@
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
             runningTasks = emptyList(),
-            recentTaskPackages = listOf(RECENT_SPLIT_PACKAGES_1, RECENT_PACKAGE_1, RECENT_PACKAGE_2)
+            recentTaskPackages = listOf(RECENT_SPLIT_PACKAGES_1, RECENT_PACKAGE_1, RECENT_PACKAGE_2),
         )
         val shownPackages = recentAppsController.shownTasks.map { it.packageNames }
         // Only 2 recent tasks shown: Pair + 1 Recent Task
@@ -661,14 +655,14 @@
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
             runningTasks = emptyList(),
-            recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2)
+            recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2),
         )
         verify(taskbarViewController, times(1)).commitRunningAppsToUI()
         // Call onRecentTasksChanged() again with the same tasks, verify it's a no-op.
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
             runningTasks = emptyList(),
-            recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2)
+            recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2),
         )
         verify(taskbarViewController, times(1)).commitRunningAppsToUI()
     }
@@ -681,14 +675,14 @@
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
             runningTasks = listOf(runningTask1, runningTask2),
-            recentTaskPackages = emptyList()
+            recentTaskPackages = emptyList(),
         )
         verify(taskbarViewController, times(1)).commitRunningAppsToUI()
         // Call onRecentTasksChanged() again with the same tasks, verify it's a no-op.
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
             runningTasks = listOf(runningTask1, runningTask2),
-            recentTaskPackages = emptyList()
+            recentTaskPackages = emptyList(),
         )
         verify(taskbarViewController, times(1)).commitRunningAppsToUI()
     }
@@ -702,7 +696,7 @@
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
             runningTasks = listOf(task1Minimized, task2Visible),
-            recentTaskPackages = emptyList()
+            recentTaskPackages = emptyList(),
         )
         verify(taskbarViewController, times(1)).commitRunningAppsToUI()
 
@@ -710,7 +704,7 @@
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = emptyList(),
             runningTasks = listOf(task1Minimized, task2Minimized),
-            recentTaskPackages = emptyList()
+            recentTaskPackages = emptyList(),
         )
 
         verify(taskbarViewController, times(2)).commitRunningAppsToUI()
@@ -726,7 +720,7 @@
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = hotseatPackages,
             runningTasks = originalTasks,
-            recentTaskPackages = emptyList()
+            recentTaskPackages = emptyList(),
         )
         verify(taskbarViewController, times(1)).commitRunningAppsToUI()
 
@@ -734,7 +728,7 @@
         prepareHotseatAndRunningAndRecentApps(
             hotseatPackages = hotseatPackages,
             runningTasks = newTasks,
-            recentTaskPackages = emptyList()
+            recentTaskPackages = emptyList(),
         )
 
         verify(taskbarViewController, times(2)).commitRunningAppsToUI()
@@ -751,10 +745,7 @@
         return recentAppsController.shownHotseatItems.toTypedArray()
     }
 
-    private fun updateRecentTasks(
-        runningTasks: List<Task>,
-        recentTaskPackages: List<String>,
-    ) {
+    private fun updateRecentTasks(runningTasks: List<Task>, recentTaskPackages: List<String>) {
         val recentTasks = createRecentTasksFromPackageNames(recentTaskPackages)
         val allTasks =
             ArrayList<GroupTask>().apply {
@@ -790,7 +781,7 @@
 
     private fun createTestAppInfo(
         packageName: String = "testPackageName",
-        className: String = "testClassName"
+        className: String = "testClassName",
     ) = AppInfo(ComponentName(packageName, className), className /* title */, userHandle, Intent())
 
     private fun createRecentTasksFromPackageNames(packageNames: List<String>): List<GroupTask> {
@@ -800,7 +791,7 @@
                 GroupTask(
                     createTask(100, splitPackages[0]),
                     createTask(101, splitPackages[1]),
-                    /* splitBounds = */ null
+                    /* splitBounds = */ null,
                 )
             } else {
                 // Use the number at the end of the test packageName as the id.
@@ -818,14 +809,15 @@
                     Intent().apply { `package` = packageName },
                     ComponentName(packageName, "TestActivity"),
                     userHandle.identifier,
-                    0
+                    0,
                 )
             )
             .apply { this.isVisible = isVisible }
     }
 
     private fun setInDesktopMode(inDesktopMode: Boolean) {
-        whenever(mockDesktopVisibilityController.areDesktopTasksVisible()).thenReturn(inDesktopMode)
+        whenever(taskbarControllers.taskbarDesktopModeController.areDesktopTasksVisible)
+            .thenReturn(inDesktopMode)
     }
 
     private val GroupTask.packageNames: List<String>
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsPersistentTaskbar.java b/quickstep/tests/src/com/android/quickstep/TaplTestsPersistentTaskbar.java
index c419cd2..a16811e 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsPersistentTaskbar.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsPersistentTaskbar.java
@@ -23,7 +23,6 @@
 import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
 
 import org.junit.Assert;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -31,8 +30,6 @@
 @RunWith(AndroidJUnit4.class)
 public class TaplTestsPersistentTaskbar extends AbstractTaplTestsTaskbar {
 
-    //TODO(b/359277238): fix falling tests
-    @Ignore
     @Test
     @NavigationModeSwitch
     public void testTaskbarFillsWidth() {
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 943c1bd..3e6436b 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -516,8 +516,6 @@
                 isInState(() -> LauncherState.NORMAL));
     }
 
-    //TODO(b/359277238): fix falling tests
-    @Ignore
     @Test
     @PortraitLandscape
     @TaskbarModeSwitch
diff --git a/quickstep/tests/src/com/android/quickstep/taskbar/controllers/TaskbarPinningControllerTest.kt b/quickstep/tests/src/com/android/quickstep/taskbar/controllers/TaskbarPinningControllerTest.kt
index 4d10f0f..cb59f7d 100644
--- a/quickstep/tests/src/com/android/quickstep/taskbar/controllers/TaskbarPinningControllerTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/taskbar/controllers/TaskbarPinningControllerTest.kt
@@ -55,7 +55,6 @@
     private val taskbarDragLayer = mock<TaskbarDragLayer>()
     private val taskbarSharedState = mock<TaskbarSharedState>()
     private var isInDesktopMode = false
-    private val isInDesktopModeProvider = { isInDesktopMode }
     private val launcherPrefs =
         mock<LauncherPrefs> {
             on { get(TASKBAR_PINNING) } doReturn false
@@ -71,8 +70,9 @@
         whenever(taskbarActivityContext.launcherPrefs).thenReturn(launcherPrefs)
         whenever(taskbarActivityContext.dragLayer).thenReturn(taskbarDragLayer)
         whenever(taskbarActivityContext.statsLogManager).thenReturn(statsLogManager)
-        pinningController =
-            spy(TaskbarPinningController(taskbarActivityContext, isInDesktopModeProvider))
+        whenever(taskbarControllers.taskbarDesktopModeController.areDesktopTasksVisible)
+            .thenAnswer { _ -> isInDesktopMode }
+        pinningController = spy(TaskbarPinningController(taskbarActivityContext))
         pinningController.init(taskbarControllers, taskbarSharedState)
     }
 
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 8ae6d73..cc5baea 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -25,7 +25,6 @@
 import static com.android.launcher3.InvariantDeviceProfile.INDEX_TWO_PANEL_PORTRAIT;
 import static com.android.launcher3.Utilities.dpiFromPx;
 import static com.android.launcher3.Utilities.pxFromSp;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_MULTI_DISPLAY_PARTIAL_DEPTH;
 import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ICON_OVERLAP_FACTOR;
 import static com.android.launcher3.icons.GraphicsUtils.getShapePath;
 import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
@@ -503,7 +502,7 @@
         bottomSheetCloseDuration = res.getInteger(R.integer.config_bottomSheetCloseDuration);
         if (isTablet) {
             bottomSheetWorkspaceScale = workspaceContentScale;
-            if (isMultiDisplay && !ENABLE_MULTI_DISPLAY_PARTIAL_DEPTH.get()) {
+            if (isMultiDisplay) {
                 // TODO(b/259893832): Revert to use maxWallpaperScale to calculate bottomSheetDepth
                 // when screen recorder bug is fixed.
                 if (enableScalingRevealHomeAnimation()) {
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index bafb528..365e3d4 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -241,6 +241,7 @@
 import com.android.launcher3.util.ScreenOnTracker;
 import com.android.launcher3.util.ScreenOnTracker.ScreenOnListener;
 import com.android.launcher3.util.SettingsCache;
+import com.android.launcher3.util.StableViewInfo;
 import com.android.launcher3.util.SystemUiController;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.Thunk;
@@ -2424,17 +2425,16 @@
      * Similar to {@link #getFirstMatch} but optimized to finding a suitable view for the app close
      * animation.
      *
-     * @param preferredItemId The id of the preferred item to match to if it exists,
-     *                        or ItemInfo#NO_MATCHING_ID if you want to not match by item id
+     * @param svi The StableViewInfo of the preferred item to match to if it exists or null
      * @param packageName The package name of the app to match.
      * @param user The user of the app to match.
      * @param supportsAllAppsState If true and we are in All Apps state, looks for view in All Apps.
      *                             Else we only looks on the workspace.
      */
-    public @Nullable View getFirstMatchForAppClose(int preferredItemId, String packageName,
+    public @Nullable View getFirstMatchForAppClose(
+            @Nullable StableViewInfo svi, String packageName,
             UserHandle user, boolean supportsAllAppsState) {
-        final Predicate<ItemInfo> preferredItem = info ->
-                info != null && info.id == preferredItemId;
+        final Predicate<ItemInfo> preferredItem = svi == null ? i -> false : svi::matches;
         final Predicate<ItemInfo> packageAndUserAndApp = info ->
                 info != null
                         && info.itemType == ITEM_TYPE_APPLICATION
@@ -2525,6 +2525,9 @@
         final int itemCount = container.getChildCount();
         for (int itemIdx = 0; itemIdx < itemCount; itemIdx++) {
             View item = container.getChildAt(itemIdx);
+            if (item.getVisibility() != View.VISIBLE) {
+                continue;
+            }
             if (item instanceof ViewGroup viewGroup) {
                 View view = mapOverViewGroup(viewGroup, op);
                 if (view != null) {
@@ -3036,6 +3039,7 @@
         return mPopupDataProvider.getDotInfoForItem(info);
     }
 
+    @NonNull
     public LauncherOverlayManager getOverlayManager() {
         return mOverlayManager;
     }
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 365fbd3..0ec3b79 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -1463,6 +1463,15 @@
                 mEdgeGlowLeft.onFlingVelocity(velocity);
                 mEdgeGlowRight.onFlingVelocity(velocity);
             }
+
+            // Detect if user tries to swipe to -1 page but gets disallowed by checking if there was
+            // left-over values in mEdgeGlowLeft (or mEdgeGlowRight in RLT).
+            final int layoutDir = getLayoutDirection();
+            if ((mEdgeGlowLeft.getDistance() > 0 && layoutDir == LAYOUT_DIRECTION_LTR)
+                    || (mEdgeGlowRight.getDistance() > 0 && layoutDir == LAYOUT_DIRECTION_RTL)) {
+                onDisallowSwipeToMinusOnePage();
+            }
+
             mEdgeGlowLeft.onRelease(ev);
             mEdgeGlowRight.onRelease(ev);
             // End any intermediate reordering states
@@ -1487,6 +1496,8 @@
         return true;
     }
 
+    protected void onDisallowSwipeToMinusOnePage() {}
+
     protected void onNotSnappingToPageInFreeScroll() { }
 
     /**
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 255260e..0e9c861 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -1123,6 +1123,11 @@
         return super.onTouchEvent(ev);
     }
 
+    @Override
+    protected void onDisallowSwipeToMinusOnePage() {
+        mLauncher.getOverlayManager().onDisallowSwipeToMinusOnePage();
+    }
+
     /**
      * Called directly from a CellLayout (not by the framework), after we've been added as a
      * listener via setOnInterceptTouchEventListener(). This allows us to tell the CellLayout
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 1b0ad04..742648e 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -37,7 +37,6 @@
 
 import android.animation.Animator;
 import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
 import android.util.FloatProperty;
 import android.view.HapticFeedbackConstants;
 import android.view.View;
@@ -52,7 +51,6 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatedFloat;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.anim.PropertySetter;
@@ -359,22 +357,6 @@
             });
         }
 
-        if (FeatureFlags.ENABLE_PREMIUM_HAPTICS_ALL_APPS.get() && config.isUserControlled()
-                && Utilities.ATLEAST_S) {
-            if (toState == ALL_APPS) {
-                builder.addOnFrameListener(
-                        new VibrationAnimatorUpdateListener(this, mVibratorWrapper,
-                                SWIPE_DRAG_COMMIT_THRESHOLD, 1));
-            } else {
-                builder.addOnFrameListener(
-                        new VibrationAnimatorUpdateListener(this, mVibratorWrapper,
-                                0, SWIPE_DRAG_COMMIT_THRESHOLD));
-            }
-            builder.addEndListener((unused) -> {
-                mVibratorWrapper.cancelVibrate();
-            });
-        }
-
         float targetProgress = toState.getVerticalProgress(mLauncher);
         if (Float.compare(mProgress, targetProgress) == 0) {
             setAlphas(toState, config, builder);
@@ -391,8 +373,7 @@
 
         setAlphas(toState, config, builder);
         // This controls both haptics for tapping on QSB and going to all apps.
-        if (ALL_APPS.equals(toState) && mLauncher.isInState(NORMAL) &&
-                !FeatureFlags.ENABLE_PREMIUM_HAPTICS_ALL_APPS.get()) {
+        if (ALL_APPS.equals(toState) && mLauncher.isInState(NORMAL)) {
             mLauncher.getAppsView().performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
                     HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
         }
@@ -445,45 +426,4 @@
     public void setShiftRange(float shiftRange) {
         mShiftRange = shiftRange;
     }
-
-    /**
-     * This VibrationAnimatorUpdateListener class takes in four parameters, a controller, start
-     * threshold, end threshold, and a Vibrator wrapper. We use the progress given by the controller
-     * as it gives an accurate progress that dictates where the vibrator should vibrate.
-     * Note: once the user begins a gesture and does the commit haptic, there should not be anymore
-     * haptics played for that gesture.
-     */
-    private static class VibrationAnimatorUpdateListener implements
-            ValueAnimator.AnimatorUpdateListener {
-        private final VibratorWrapper mVibratorWrapper;
-        private final AllAppsTransitionController mController;
-        private final float mStartThreshold;
-        private final float mEndThreshold;
-        private boolean mHasCommitted;
-
-        VibrationAnimatorUpdateListener(AllAppsTransitionController controller,
-                                        VibratorWrapper vibratorWrapper, float startThreshold,
-                                        float endThreshold) {
-            mController = controller;
-            mVibratorWrapper = vibratorWrapper;
-            mStartThreshold = startThreshold;
-            mEndThreshold = endThreshold;
-        }
-
-        @Override
-        public void onAnimationUpdate(ValueAnimator animation) {
-            if (mHasCommitted) {
-                return;
-            }
-            float currentProgress =
-                    AllAppsTransitionController.ALL_APPS_PROGRESS.get(mController);
-            if (currentProgress > mStartThreshold && currentProgress < mEndThreshold) {
-                mVibratorWrapper.vibrateForDragTexture();
-            } else if (!(currentProgress == 0 || currentProgress == 1)) {
-                // This check guards against committing at the location of the start of the gesture
-                mVibratorWrapper.vibrateForDragCommit();
-                mHasCommitted = true;
-            }
-        }
-    }
 }
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index d0596fa..092b524 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -62,11 +62,6 @@
      * and set a default value for the flag. This will be the default value on Debug builds.
      * <p>
      */
-    // TODO(Block 2): Clean up flags
-    public static final BooleanFlag ENABLE_MULTI_DISPLAY_PARTIAL_DEPTH = getDebugFlag(270395073,
-            "ENABLE_MULTI_DISPLAY_PARTIAL_DEPTH", DISABLED,
-            "Allow bottom sheet depth to be smaller than 1 for multi-display devices.");
-
     // TODO(Block 3): Clean up flags
     public static final BooleanFlag ENABLE_DISMISS_PREDICTION_UNDO = getDebugFlag(270394476,
             "ENABLE_DISMISS_PREDICTION_UNDO", DISABLED,
@@ -85,26 +80,10 @@
                     + "data preparation for loading the home screen");
 
     // TODO(Block 4): Cleanup flags
-    public static final BooleanFlag ENABLE_ALL_APPS_FROM_OVERVIEW =
-            getDebugFlag(275132633, "ENABLE_ALL_APPS_FROM_OVERVIEW", DISABLED,
-                    "Allow entering All Apps from Overview (e.g. long swipe up from app)");
-
     public static final BooleanFlag ENABLE_SHOW_KEYBOARD_OPTION_IN_ALL_APPS = getReleaseFlag(
             270394468, "ENABLE_SHOW_KEYBOARD_OPTION_IN_ALL_APPS", ENABLED,
             "Enable option to show keyboard when going to all-apps");
 
-    // TODO(Block 5): Clean up flags
-    public static final BooleanFlag ENABLE_TWOLINE_DEVICESEARCH = getDebugFlag(201388851,
-            "ENABLE_TWOLINE_DEVICESEARCH", DISABLED,
-            "Enable two line label for icons with labels on device search.");
-
-    public static final BooleanFlag ENABLE_ICON_IN_TEXT_HEADER = getDebugFlag(270395143,
-            "ENABLE_ICON_IN_TEXT_HEADER", DISABLED, "Show icon in textheader");
-
-    public static final BooleanFlag ENABLE_PREMIUM_HAPTICS_ALL_APPS = getDebugFlag(270396358,
-            "ENABLE_PREMIUM_HAPTICS_ALL_APPS", DISABLED,
-            "Enables haptics opening/closing All apps");
-
     // TODO(Block 6): Clean up flags
     public static final BooleanFlag ENABLE_ALL_APPS_SEARCH_IN_TASKBAR = getDebugFlag(270393900,
             "ENABLE_ALL_APPS_SEARCH_IN_TASKBAR", ENABLED,
@@ -125,10 +104,6 @@
     public static final BooleanFlag FOLDABLE_SINGLE_PAGE = getDebugFlag(270395274,
             "FOLDABLE_SINGLE_PAGE", DISABLED, "Use a single page for the workspace");
 
-    public static final BooleanFlag ENABLE_PARAMETRIZE_REORDER = getDebugFlag(289420844,
-            "ENABLE_PARAMETRIZE_REORDER", DISABLED,
-            "Enables generating the reorder using a set of parameters");
-
     // TODO(Block 12): Clean up flags
     public static final BooleanFlag ENABLE_MULTI_INSTANCE = getDebugFlag(270396680,
             "ENABLE_MULTI_INSTANCE", DISABLED,
@@ -175,10 +150,6 @@
     public static final BooleanFlag PROMISE_APPS_IN_ALL_APPS = getDebugFlag(270390012,
             "PROMISE_APPS_IN_ALL_APPS", DISABLED, "Add promise icon in all-apps");
 
-    public static final BooleanFlag KEYGUARD_ANIMATION = getDebugFlag(270390904,
-            "KEYGUARD_ANIMATION", DISABLED,
-            "Enable animation for keyguard going away on wallpaper");
-
     public static final BooleanFlag ENABLE_DEVICE_SEARCH = getReleaseFlag(270390907,
             "ENABLE_DEVICE_SEARCH", ENABLED, "Allows on device search in all apps");
 
@@ -235,9 +206,6 @@
             "ENABLE_SEARCH_UNINSTALLED_APPS", ENABLED, "Search uninstalled app results.");
 
     // TODO(Block 20): Clean up flags
-    public static final BooleanFlag ENABLE_SCRIM_FOR_APP_LAUNCH = getDebugFlag(270393276,
-            "ENABLE_SCRIM_FOR_APP_LAUNCH", DISABLED, "Enables scrim during app launch animation.");
-
     public static final BooleanFlag ENABLE_BACK_SWIPE_HOME_ANIMATION = getDebugFlag(270393426,
             "ENABLE_BACK_SWIPE_HOME_ANIMATION", ENABLED,
             "Enables home animation to icon when user swipes back.");
diff --git a/src/com/android/launcher3/graphics/SysUiScrim.java b/src/com/android/launcher3/graphics/SysUiScrim.java
index 260d490..077ddfc 100644
--- a/src/com/android/launcher3/graphics/SysUiScrim.java
+++ b/src/com/android/launcher3/graphics/SysUiScrim.java
@@ -18,8 +18,6 @@
 import static android.graphics.Paint.DITHER_FLAG;
 import static android.graphics.Paint.FILTER_BITMAP_FLAG;
 
-import static com.android.launcher3.config.FeatureFlags.KEYGUARD_ANIMATION;
-
 import android.animation.ObjectAnimator;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
@@ -111,7 +109,7 @@
                 new int[]{0x00FFFFFF, 0x2FFFFFFF},
                 new float[]{0f, 1f});
 
-        if (!KEYGUARD_ANIMATION.get() && !mHideSysUiScrim) {
+        if (!mHideSysUiScrim) {
             view.addOnAttachStateChangeListener(this);
         }
     }
diff --git a/src/com/android/launcher3/model/GridSizeMigrationUtil.java b/src/com/android/launcher3/model/GridSizeMigrationUtil.java
index f54fc57..8d2a7f9 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationUtil.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationUtil.java
@@ -121,13 +121,21 @@
             @NonNull DeviceGridState destDeviceState,
             @NonNull DatabaseHelper target,
             @NonNull SQLiteDatabase source) {
+
+        Log.i("b/360462379", "Going from " + srcDeviceState.getColumns() + "x"
+                + srcDeviceState.getRows());
+        Log.i("b/360462379", "Going to " + destDeviceState.getColumns() + "x"
+                + destDeviceState.getRows());
+
         if (!needsToMigrate(srcDeviceState, destDeviceState)) {
+            Log.i("b/360462379", "Does not need to migrate.");
             return true;
         }
 
         if (Flags.enableGridMigrationFix()
                 && srcDeviceState.getColumns().equals(destDeviceState.getColumns())
                 && srcDeviceState.getRows() < destDeviceState.getRows()) {
+            Log.i("b/360462379", "Grid migration fix entry point.");
             // Only use this strategy when comparing the previous grid to the new grid and the
             // columns are the same and the destination has more rows
             copyTable(source, TABLE_NAME, target.getWritableDatabase(), TABLE_NAME, context);
diff --git a/src/com/android/launcher3/model/data/ItemInfo.java b/src/com/android/launcher3/model/data/ItemInfo.java
index 62198cb..b706d24 100644
--- a/src/com/android/launcher3/model/data/ItemInfo.java
+++ b/src/com/android/launcher3/model/data/ItemInfo.java
@@ -81,8 +81,6 @@
 
     public static final boolean DEBUG = false;
     public static final int NO_ID = -1;
-    // An id that doesn't match any item, including predicted apps with have an id=NO_ID
-    public static final int NO_MATCHING_ID = Integer.MIN_VALUE;
 
     /** Hidden field Settings.Secure.NAV_BAR_KIDS_MODE */
     private static final Uri NAV_BAR_KIDS_MODE = Settings.Secure.getUriFor("nav_bar_kids_mode");
@@ -545,6 +543,14 @@
         this.title = title;
     }
 
+    /**
+     * Returns a string ID that is stable for a user session, but may not be persisted
+     */
+    @Nullable
+    public Object getStableId() {
+        return getComponentKey();
+    }
+
     private int getUserType(UserIconInfo info) {
         if (info == null) {
             return SysUiStatsLog.LAUNCHER_UICHANGED__USER_TYPE__TYPE_UNKNOWN;
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index 0c90eb9..1b245ab 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -7,7 +7,6 @@
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_WIDGETS_TAP;
 import static com.android.launcher3.widget.picker.model.data.WidgetPickerDataUtils.findAllWidgetsForPackageUser;
 
-import android.app.ActivityOptions;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -35,6 +34,7 @@
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.pm.UserCache;
+import com.android.launcher3.util.ActivityOptionsWrapper;
 import com.android.launcher3.util.ApiWrapper;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.InstantAppResolver;
@@ -184,10 +184,12 @@
 
         @Override
         public void onClick(View view) {
-            dismissTaskMenuView();
             Rect sourceBounds = Utilities.getViewBounds(view);
+            ActivityOptionsWrapper options = mTarget.getActivityLaunchOptions(view, mItemInfo);
+            // Dismiss the taskMenu when the app launch animation is complete
+            options.onEndCallback.add(this::dismissTaskMenuView);
             PackageManagerHelper.startDetailsActivityForInfo(view.getContext(), mItemInfo,
-                    sourceBounds, ActivityOptions.makeBasic().toBundle());
+                    sourceBounds, options.toBundle());
             mTarget.getStatsLogManager().logger().withItemInfo(mItemInfo)
                     .log(LAUNCHER_SYSTEM_SHORTCUT_APP_INFO_TAP);
         }
diff --git a/src/com/android/launcher3/touch/AllAppsSwipeController.java b/src/com/android/launcher3/touch/AllAppsSwipeController.java
index fe4a83b..9dcdf22 100644
--- a/src/com/android/launcher3/touch/AllAppsSwipeController.java
+++ b/src/com/android/launcher3/touch/AllAppsSwipeController.java
@@ -22,13 +22,9 @@
 import static com.android.app.animation.Interpolators.FINAL_FRAME;
 import static com.android.app.animation.Interpolators.INSTANT;
 import static com.android.app.animation.Interpolators.LINEAR;
-import static com.android.app.animation.Interpolators.clampToProgress;
-import static com.android.app.animation.Interpolators.mapToProgress;
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_BOTTOM_SHEET_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_KEYBOARD_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_DEPTH;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_SCALE;
@@ -37,15 +33,12 @@
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_SCALE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_TRANSLATE;
-import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
 
 import android.view.MotionEvent;
 import android.view.animation.Interpolator;
 
 import com.android.app.animation.Interpolators;
 import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.states.StateAnimationConfig;
@@ -281,36 +274,6 @@
         }
     }
 
-    /**
-     * Applies Animation config values for transition from overview to all apps.
-     *
-     * @param threshold progress at which all apps will open upon release
-     */
-    public static void applyOverviewToAllAppsAnimConfig(
-            DeviceProfile deviceProfile, StateAnimationConfig config, float threshold) {
-        config.animProps |= StateAnimationConfig.USER_CONTROLLED;
-        config.animFlags = SKIP_OVERVIEW;
-        if (deviceProfile.isTablet) {
-            config.setInterpolator(ANIM_ALL_APPS_FADE, INSTANT);
-            config.setInterpolator(ANIM_SCRIM_FADE, ALL_APPS_SCRIM_RESPONDER);
-            // The fact that we end on Workspace is not very ideal, but since we do, fade it in at
-            // the end of the transition. Don't scale/translate it.
-            config.setInterpolator(ANIM_WORKSPACE_FADE, clampToProgress(LINEAR, 0.8f, 1));
-            config.setInterpolator(ANIM_WORKSPACE_SCALE, INSTANT);
-            config.setInterpolator(ANIM_WORKSPACE_TRANSLATE, INSTANT);
-        } else {
-            // Pop the background panel, keyboard, and content in at full opacity at the threshold.
-            config.setInterpolator(ANIM_ALL_APPS_BOTTOM_SHEET_FADE,
-                    thresholdInterpolator(threshold, INSTANT));
-            config.setInterpolator(ANIM_ALL_APPS_KEYBOARD_FADE,
-                    thresholdInterpolator(threshold, INSTANT));
-            config.setInterpolator(ANIM_ALL_APPS_FADE, thresholdInterpolator(threshold, INSTANT));
-
-            config.setInterpolator(ANIM_VERTICAL_PROGRESS,
-                    thresholdInterpolator(threshold, mapToProgress(LINEAR, threshold, 1f)));
-        }
-    }
-
     /** Creates an interpolator that is 0 until the threshold, then follows given interpolator. */
     private static Interpolator thresholdInterpolator(float threshold, Interpolator interpolator) {
         return progress -> progress <= threshold ? 0 : interpolator.getInterpolation(progress);
diff --git a/src/com/android/launcher3/util/ActivityOptionsWrapper.java b/src/com/android/launcher3/util/ActivityOptionsWrapper.java
index 99cc1f7..17ff2a9 100644
--- a/src/com/android/launcher3/util/ActivityOptionsWrapper.java
+++ b/src/com/android/launcher3/util/ActivityOptionsWrapper.java
@@ -25,6 +25,7 @@
 public class ActivityOptionsWrapper {
 
     public final ActivityOptions options;
+    // Called when the app launch animation is complete
     public final RunnableList onEndCallback;
 
     public ActivityOptionsWrapper(ActivityOptions options, RunnableList onEndCallback) {
diff --git a/src/com/android/launcher3/util/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java
index b1e82bb..072bcdf 100644
--- a/src/com/android/launcher3/util/DisplayController.java
+++ b/src/com/android/launcher3/util/DisplayController.java
@@ -187,11 +187,6 @@
         return INSTANCE.get(context).getInfo().isTransientTaskbar();
     }
 
-    /** Returns whether we are currently in Desktop mode. */
-    public static boolean isInDesktopMode(Context context) {
-        return INSTANCE.get(context).getInfo().isInDesktopMode();
-    }
-
     /**
      * Handles info change for desktop mode.
      */
@@ -496,10 +491,6 @@
             return navigationMode == NavigationMode.NO_BUTTON && !isTransientTaskbar();
         }
 
-        public boolean isInDesktopMode() {
-            return mIsInDesktopMode;
-        }
-
         /**
          * Returns {@code true} if the bounds represent a tablet.
          */
diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java
index 8c5a76e..469e363 100644
--- a/src/com/android/launcher3/util/PackageManagerHelper.java
+++ b/src/com/android/launcher3/util/PackageManagerHelper.java
@@ -212,7 +212,7 @@
         if (info instanceof ItemInfoWithIcon appInfo
                 && (appInfo.runtimeStatusFlags & FLAG_INSTALL_SESSION_ACTIVE) != 0) {
             context.startActivity(ApiWrapper.INSTANCE.get(context).getAppMarketActivityIntent(
-                    appInfo.getTargetComponent().getPackageName(), Process.myUserHandle()));
+                    appInfo.getTargetComponent().getPackageName(), Process.myUserHandle()), opts);
             return;
         }
         ComponentName componentName = null;
diff --git a/src/com/android/launcher3/util/StableViewInfo.kt b/src/com/android/launcher3/util/StableViewInfo.kt
new file mode 100644
index 0000000..29dcd59
--- /dev/null
+++ b/src/com/android/launcher3/util/StableViewInfo.kt
@@ -0,0 +1,56 @@
+/*
+ * 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.util
+
+import android.os.IBinder
+import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.model.data.ItemInfo.NO_ID
+
+/** Info parameters that can be used to identify a Launcher object */
+data class StableViewInfo(val itemId: Int, val containerId: Int, val stableId: Any) {
+
+    fun matches(info: ItemInfo?) =
+        info != null &&
+            itemId == info.id &&
+            containerId == info.container &&
+            stableId == info.stableId
+
+    companion object {
+
+        private fun ItemInfo.toStableViewInfo() =
+            stableId?.let { sId ->
+                if (id != NO_ID || container != NO_ID) StableViewInfo(id, container, sId) else null
+            }
+
+        /**
+         * Return a new launch cookie for the activity launch if supported.
+         *
+         * @param info the item info for the launch
+         */
+        @JvmStatic
+        fun toLaunchCookie(info: ItemInfo?) =
+            info?.toStableViewInfo()?.let { ObjectWrapper.wrap(it) }
+
+        /**
+         * Unwraps the binder and returns the first non-null StableViewInfo in the list or null if
+         * none can be found
+         */
+        @JvmStatic
+        fun fromLaunchCookies(launchCookies: List<IBinder>) =
+            launchCookies.firstNotNullOfOrNull { ObjectWrapper.unwrap<StableViewInfo>(it) }
+    }
+}
diff --git a/src/com/android/launcher3/util/VibratorWrapper.java b/src/com/android/launcher3/util/VibratorWrapper.java
index 6bae1ba..a4b8eb0 100644
--- a/src/com/android/launcher3/util/VibratorWrapper.java
+++ b/src/com/android/launcher3/util/VibratorWrapper.java
@@ -25,12 +25,10 @@
 import android.content.Context;
 import android.media.AudioAttributes;
 import android.net.Uri;
-import android.os.SystemClock;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
 import android.provider.Settings;
 
-import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
 import com.android.launcher3.Utilities;
@@ -54,20 +52,6 @@
     static final Uri HAPTIC_FEEDBACK_URI = Settings.System.getUriFor(HAPTIC_FEEDBACK_ENABLED);
 
     @VisibleForTesting static final float LOW_TICK_SCALE = 0.9f;
-    @VisibleForTesting static final float DRAG_TEXTURE_SCALE = 0.03f;
-    @VisibleForTesting static final float DRAG_COMMIT_SCALE = 0.5f;
-    @VisibleForTesting static final float DRAG_BUMP_SCALE = 0.4f;
-    @VisibleForTesting static final int DRAG_TEXTURE_EFFECT_SIZE = 200;
-
-    @Nullable
-    private final VibrationEffect mDragEffect;
-    @Nullable
-    private final VibrationEffect mCommitEffect;
-    @Nullable
-    private final VibrationEffect mBumpEffect;
-
-    private long mLastDragTime;
-    private final int mThresholdUntilNextDragCallMillis;
 
     /**
      * Haptic when entering overview.
@@ -100,28 +84,6 @@
         } else {
             mIsHapticFeedbackEnabled = false;
         }
-
-        if (Utilities.ATLEAST_S && mVibrator.areAllPrimitivesSupported(
-                PRIMITIVE_LOW_TICK)) {
-
-            // Drag texture, Commit, and Bump should only be used for premium phones.
-            // Before using these haptics make sure check if the device can use it
-            mDragEffect = getDragEffect();
-            mCommitEffect = VibrationEffect.startComposition().addPrimitive(
-                    VibrationEffect.Composition.PRIMITIVE_TICK, DRAG_COMMIT_SCALE).compose();
-            mBumpEffect = VibrationEffect.startComposition().addPrimitive(
-                    PRIMITIVE_LOW_TICK, DRAG_BUMP_SCALE).compose();
-            int primitiveDuration = mVibrator.getPrimitiveDurations(
-                    PRIMITIVE_LOW_TICK)[0];
-
-            mThresholdUntilNextDragCallMillis =
-                    DRAG_TEXTURE_EFFECT_SIZE * primitiveDuration + 100;
-        } else {
-            mDragEffect = null;
-            mCommitEffect = null;
-            mBumpEffect = null;
-            mThresholdUntilNextDragCallMillis = 0;
-        }
     }
 
     @Override
@@ -132,52 +94,11 @@
     }
 
     /**
-     * This is called when the user swipes to/from all apps. This is meant to be used in between
-     * long animation progresses so that it gives a dragging texture effect. For a better
-     * experience, this should be used in combination with vibrateForDragCommit().
-     */
-    public void vibrateForDragTexture() {
-        if (mDragEffect == null) {
-            return;
-        }
-        long currentTime = SystemClock.elapsedRealtime();
-        long elapsedTimeSinceDrag = currentTime - mLastDragTime;
-        if (elapsedTimeSinceDrag >= mThresholdUntilNextDragCallMillis) {
-            vibrate(mDragEffect);
-            mLastDragTime = currentTime;
-        }
-    }
-
-    /**
-     * This is used when user reaches the commit threshold when swiping to/from from all apps.
-     */
-    public void vibrateForDragCommit() {
-        if (mCommitEffect != null) {
-            vibrate(mCommitEffect);
-        }
-        // resetting dragTexture timestamp to be able to play dragTexture again
-        mLastDragTime = 0;
-    }
-
-    /**
-     * The bump haptic is used to be called at the end of a swipe and only if it the gesture is a
-     * FLING going to/from all apps. Client can just call this method elsewhere just for the
-     * effect.
-     */
-    public void vibrateForDragBump() {
-        if (mBumpEffect != null) {
-            vibrate(mBumpEffect);
-        }
-    }
-
-    /**
      * This should be used to cancel a haptic in case where the haptic shouldn't be vibrating. For
      * example, when no animation is happening but a vibrator happens to be vibrating still.
      */
     public void cancelVibrate() {
         UI_HELPER_EXECUTOR.execute(mVibrator::cancel);
-        // reset dragTexture timestamp to be able to play dragTexture again whenever cancelled
-        mLastDragTime = 0;
     }
 
     /** Vibrates with the given effect if haptic feedback is available and enabled. */
@@ -217,13 +138,4 @@
             vibrate(primitiveLowTickEffect);
         }
     }
-
-    static VibrationEffect getDragEffect() {
-        VibrationEffect.Composition dragEffect = VibrationEffect.startComposition();
-        for (int i = 0; i < DRAG_TEXTURE_EFFECT_SIZE; i++) {
-            dragEffect.addPrimitive(
-                    PRIMITIVE_LOW_TICK, DRAG_TEXTURE_SCALE);
-        }
-        return dragEffect.compose();
-    }
 }
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index 4ee6aff..6739387 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -253,11 +253,14 @@
     public static void getLocationBoundsForView(Launcher launcher, View v, boolean isOpening,
             RectF outRect, Rect outViewBounds) {
         boolean ignoreTransform = !isOpening;
-        if (v instanceof BubbleTextHolder) {
-            v = ((BubbleTextHolder) v).getBubbleText();
+        if (v instanceof DeepShortcutView dsv) {
+            v = dsv.getIconView();
             ignoreTransform = false;
-        } else if (v.getParent() instanceof DeepShortcutView) {
-            v = ((DeepShortcutView) v.getParent()).getIconView();
+        } else if (v.getParent() instanceof DeepShortcutView dsv) {
+            v = dsv.getIconView();
+            ignoreTransform = false;
+        } else if (v instanceof BubbleTextHolder bth) {
+            v = bth.getBubbleText();
             ignoreTransform = false;
         }
         if (v == null) {
@@ -298,10 +301,10 @@
 
         Drawable badge = null;
         if (info instanceof SystemShortcut) {
-            if (originalView instanceof ImageView) {
-                drawable = ((ImageView) originalView).getDrawable();
-            } else if (originalView instanceof DeepShortcutView) {
-                drawable = ((DeepShortcutView) originalView).getIconView().getBackground();
+            if (originalView instanceof ImageView iv) {
+                drawable = iv.getDrawable();
+            } else if (originalView instanceof DeepShortcutView dsv) {
+                drawable = dsv.getIconView().getBackground();
             } else {
                 drawable = originalView.getBackground();
             }
diff --git a/src/com/android/launcher3/views/FloatingSurfaceView.java b/src/com/android/launcher3/views/FloatingSurfaceView.java
index 7fa7517..5f8e2c0 100644
--- a/src/com/android/launcher3/views/FloatingSurfaceView.java
+++ b/src/com/android/launcher3/views/FloatingSurfaceView.java
@@ -15,7 +15,6 @@
  */
 package com.android.launcher3.views;
 
-import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
 import static com.android.launcher3.views.FloatingIconView.getLocationBoundsForView;
 import static com.android.launcher3.views.FloatingIconViewCompanion.setPropertiesVisible;
 
@@ -160,7 +159,7 @@
         if (mContract == null) {
             return;
         }
-        View icon = mLauncher.getFirstMatchForAppClose(NO_MATCHING_ID,
+        View icon = mLauncher.getFirstMatchForAppClose(null /* StableViewInfo */,
                 mContract.componentName.getPackageName(), mContract.user,
                 false /* supportsAllAppsState */);
 
diff --git a/src_plugins/com/android/systemui/plugins/shared/LauncherOverlayManager.java b/src_plugins/com/android/systemui/plugins/shared/LauncherOverlayManager.java
index a940774..eaa9ef0 100644
--- a/src_plugins/com/android/systemui/plugins/shared/LauncherOverlayManager.java
+++ b/src_plugins/com/android/systemui/plugins/shared/LauncherOverlayManager.java
@@ -49,6 +49,8 @@
 
     default void onActivityDestroyed() { }
 
+    default void onDisallowSwipeToMinusOnePage() {}
+
     /**
      * @deprecated use LauncherOverlayTouchProxy directly
      */
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/VibratorWrapperTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/VibratorWrapperTest.kt
index 330c394..d321e41 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/VibratorWrapperTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/util/VibratorWrapperTest.kt
@@ -17,7 +17,6 @@
 package com.android.launcher3.util
 
 import android.media.AudioAttributes
-import android.os.SystemClock
 import android.os.VibrationEffect
 import android.os.VibrationEffect.Composition.PRIMITIVE_LOW_TICK
 import android.os.VibrationEffect.Composition.PRIMITIVE_TICK
@@ -35,13 +34,11 @@
 import org.mockito.Captor
 import org.mockito.Mock
 import org.mockito.Mockito.any
-import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 import org.mockito.kotlin.never
 import org.mockito.kotlin.same
-import org.mockito.kotlin.verifyNoMoreInteractions
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
@@ -118,55 +115,6 @@
     }
 
     @Test
-    fun vibrate_for_drag_bump() {
-        underTest.vibrateForDragBump()
-
-        awaitTasksCompleted()
-        verify(vibrator).vibrate(vibrationEffectCaptor.capture(), same(VIBRATION_ATTRS))
-        val expectedEffect =
-            VibrationEffect.startComposition()
-                .addPrimitive(PRIMITIVE_LOW_TICK, VibratorWrapper.DRAG_BUMP_SCALE)
-                .compose()
-        assertThat(vibrationEffectCaptor.value).isEqualTo(expectedEffect)
-    }
-
-    @Test
-    fun vibrate_for_drag_commit() {
-        underTest.vibrateForDragCommit()
-
-        awaitTasksCompleted()
-        verify(vibrator).vibrate(vibrationEffectCaptor.capture(), same(VIBRATION_ATTRS))
-        val expectedEffect =
-            VibrationEffect.startComposition()
-                .addPrimitive(PRIMITIVE_TICK, VibratorWrapper.DRAG_COMMIT_SCALE)
-                .compose()
-        assertThat(vibrationEffectCaptor.value).isEqualTo(expectedEffect)
-    }
-
-    @Test
-    fun vibrate_for_drag_texture() {
-        SystemClock.setCurrentTimeMillis(40000)
-
-        underTest.vibrateForDragTexture()
-
-        awaitTasksCompleted()
-        verify(vibrator).vibrate(vibrationEffectCaptor.capture(), same(VIBRATION_ATTRS))
-        assertThat(vibrationEffectCaptor.value).isEqualTo(VibratorWrapper.getDragEffect())
-    }
-
-    @Test
-    fun vibrate_for_drag_texture_within_time_window_noOp() {
-        SystemClock.setCurrentTimeMillis(40000)
-        underTest.vibrateForDragTexture()
-        awaitTasksCompleted()
-        reset(vibrator)
-
-        underTest.vibrateForDragTexture()
-
-        verifyNoMoreInteractions(vibrator)
-    }
-
-    @Test
     fun haptic_feedback_disabled_no_vibrate() {
         `when`(vibrator.hasVibrator()).thenReturn(false)
         underTest = VibratorWrapper(vibrator, settingsCache)
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/rule/TestStabilityRule.java b/tests/multivalentTests/src/com/android/launcher3/util/rule/TestStabilityRule.java
index ad2d8c2..6313cf0 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/rule/TestStabilityRule.java
+++ b/tests/multivalentTests/src/com/android/launcher3/util/rule/TestStabilityRule.java
@@ -47,7 +47,7 @@
                     + ")$");
     private static final Pattern PLATFORM_BUILD =
             Pattern.compile("^("
-                    + "(?<commandLine>eng\\.[a-z]+\\.[0-9]+\\.[0-9]+)|"
+                    + "(?<commandLine>eng\\..+)|"
                     + "(?<presubmit>P[0-9]+)|"
                     + "(?<postsubmit>[0-9]+)"
                     + ")$");
diff --git a/tests/tapl/com/android/launcher3/tapl/Taskbar.java b/tests/tapl/com/android/launcher3/tapl/Taskbar.java
index e6315f3..b4aaab7 100644
--- a/tests/tapl/com/android/launcher3/tapl/Taskbar.java
+++ b/tests/tapl/com/android/launcher3/tapl/Taskbar.java
@@ -52,7 +52,7 @@
 
         if (!mLauncher.isTransientTaskbar()) {
             Assert.assertEquals("Persistent taskbar should fill screen width",
-                    getVisibleBounds().width(), mLauncher.getRealDisplaySize().x);
+                    mLauncher.getRealDisplaySize().x, getVisibleBounds().width());
         }
     }