Merge "Add a test base for AbsSwipeUpHandler and its subclasses" into main
diff --git a/Android.bp b/Android.bp
index b205d0c..4274613 100644
--- a/Android.bp
+++ b/Android.bp
@@ -221,7 +221,7 @@
 android_library {
     name: "launcher-aosp-tapl",
     libs: [
-        "framework-statsd",
+        "framework-statsd.stubs.module_lib",
     ],
     static_libs: [
         "androidx.annotation_annotation",
@@ -388,7 +388,7 @@
         "quickstep/res",
     ],
     libs: [
-        "framework-statsd",
+        "framework-statsd.stubs.module_lib",
     ],
     static_libs: [
         "Launcher3ResLib",
@@ -453,7 +453,7 @@
     ],
     resource_dirs: [],
     libs: [
-        "framework-statsd",
+        "framework-statsd.stubs.module_lib",
     ],
     // Note the ordering here is important when it comes to resource
     // overriding. We want the most specific resource overrides defined
diff --git a/quickstep/res/drawable/keyboard_quick_switch_overview_button_background.xml b/quickstep/res/drawable/keyboard_quick_switch_text_button_background.xml
similarity index 78%
rename from quickstep/res/drawable/keyboard_quick_switch_overview_button_background.xml
rename to quickstep/res/drawable/keyboard_quick_switch_text_button_background.xml
index 8180293..f204920 100644
--- a/quickstep/res/drawable/keyboard_quick_switch_overview_button_background.xml
+++ b/quickstep/res/drawable/keyboard_quick_switch_text_button_background.xml
@@ -15,8 +15,7 @@
 -->
 <shape
     xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     android:shape="rectangle">
-    <solid android:color="?attr/materialColorSurfaceBright" />
-    <corners android:radius="@dimen/keyboard_quick_switch_task_view_radius" />
+    <solid android:color="@android:color/white" />
+    <corners android:radius="@dimen/keyboard_quick_switch_text_button_radius" />
 </shape>
diff --git a/quickstep/res/layout-land/keyboard_quick_switch_taskview.xml b/quickstep/res/layout-land/keyboard_quick_switch_taskview.xml
index 613edac..d1e5667 100644
--- a/quickstep/res/layout-land/keyboard_quick_switch_taskview.xml
+++ b/quickstep/res/layout-land/keyboard_quick_switch_taskview.xml
@@ -18,8 +18,8 @@
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:launcher="http://schemas.android.com/apk/res-auto"
-    android:layout_width="@dimen/keyboard_quick_switch_taskview_width"
-    android:layout_height="@dimen/keyboard_quick_switch_taskview_height"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
     android:importantForAccessibility="yes"
     android:background="@drawable/keyboard_quick_switch_task_view_background"
     android:clipToOutline="true"
@@ -27,8 +27,8 @@
 
     <androidx.constraintlayout.widget.ConstraintLayout
         android:id="@+id/content"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
+        android:layout_width="@dimen/keyboard_quick_switch_taskview_width"
+        android:layout_height="@dimen/keyboard_quick_switch_taskview_height"
 
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintBottom_toBottomOf="parent"
@@ -52,7 +52,7 @@
             android:layout_width="0dp"
             android:layout_height="match_parent"
             android:visibility="gone"
-            android:layout_marginStart="@dimen/keyboard_quick_switch_split_view_spacing"
+            android:layout_marginStart="@dimen/keyboard_quick_switch_view_small_spacing"
 
             app:layout_constraintTop_toTopOf="parent"
             app:layout_constraintBottom_toBottomOf="parent"
diff --git a/quickstep/res/layout-land/keyboard_quick_switch_taskview_square.xml b/quickstep/res/layout-land/keyboard_quick_switch_taskview_square.xml
new file mode 100644
index 0000000..0eccd8e
--- /dev/null
+++ b/quickstep/res/layout-land/keyboard_quick_switch_taskview_square.xml
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<com.android.launcher3.taskbar.KeyboardQuickSwitchTaskView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:importantForAccessibility="yes"
+    android:background="@drawable/keyboard_quick_switch_task_view_background"
+    android:clipToOutline="true"
+    launcher:focusBorderColor="?androidprv:attr/materialColorOutline">
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/content"
+        android:layout_width="0dp"
+        android:layout_height="@dimen/keyboard_quick_switch_taskview_height"
+
+        app:layout_constraintDimensionRatio="1:1"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent">
+
+        <include
+            layout="@layout/keyboard_quick_switch_taskview_thumbnail"
+            android:id="@+id/thumbnail_1"
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintBottom_toTopOf="@id/thumbnail_2"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"/>
+
+        <include
+            layout="@layout/keyboard_quick_switch_taskview_thumbnail"
+            android:id="@+id/thumbnail_2"
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            android:visibility="gone"
+            android:layout_marginTop="@dimen/keyboard_quick_switch_view_small_spacing"
+
+            app:layout_constraintTop_toBottomOf="@id/thumbnail_1"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"/>
+
+        <ImageView
+            android:id="@+id/icon_1"
+            android:layout_width="@dimen/keyboard_quick_switch_taskview_icon_size"
+            android:layout_height="@dimen/keyboard_quick_switch_taskview_icon_size"
+            android:importantForAccessibility="no"
+            android:scaleType="centerCrop"
+
+            app:layout_constraintTop_toTopOf="@id/thumbnail_1"
+            app:layout_constraintBottom_toBottomOf="@id/thumbnail_1"
+            app:layout_constraintStart_toStartOf="@id/thumbnail_1"
+            app:layout_constraintEnd_toEndOf="@id/thumbnail_1"/>
+
+        <ImageView
+            android:id="@+id/icon_2"
+            android:layout_width="@dimen/keyboard_quick_switch_taskview_icon_size"
+            android:layout_height="@dimen/keyboard_quick_switch_taskview_icon_size"
+            android:importantForAccessibility="no"
+            android:visibility="gone"
+            android:scaleType="centerCrop"
+
+            app:layout_constraintTop_toTopOf="@id/thumbnail_2"
+            app:layout_constraintBottom_toBottomOf="@id/thumbnail_2"
+            app:layout_constraintStart_toStartOf="@id/thumbnail_2"
+            app:layout_constraintEnd_toEndOf="@id/thumbnail_2"/>
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+</com.android.launcher3.taskbar.KeyboardQuickSwitchTaskView>
diff --git a/quickstep/res/layout/keyboard_quick_switch_textonly_taskview.xml b/quickstep/res/layout/keyboard_quick_switch_desktop_taskview.xml
similarity index 68%
rename from quickstep/res/layout/keyboard_quick_switch_textonly_taskview.xml
rename to quickstep/res/layout/keyboard_quick_switch_desktop_taskview.xml
index c76a2e3..c3f9e54 100644
--- a/quickstep/res/layout/keyboard_quick_switch_textonly_taskview.xml
+++ b/quickstep/res/layout/keyboard_quick_switch_desktop_taskview.xml
@@ -18,17 +18,20 @@
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:launcher="http://schemas.android.com/apk/res-auto"
-    android:layout_width="@dimen/keyboard_quick_switch_taskview_width"
-    android:layout_height="@dimen/keyboard_quick_switch_taskview_height"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
     android:clipToOutline="true"
     android:importantForAccessibility="yes"
-    launcher:focusBorderColor="?attr/materialColorOutline">
+    launcher:focusBorderColor="?androidprv:attr/materialColorOutline"
+    launcher:focusBorderRadius="@dimen/keyboard_quick_switch_text_button_radius">
 
     <androidx.constraintlayout.widget.ConstraintLayout
         android:id="@+id/content"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:background="@drawable/keyboard_quick_switch_overview_button_background"
+        android:layout_width="@dimen/keyboard_quick_switch_text_button_width"
+        android:layout_height="@dimen/keyboard_quick_switch_taskview_height"
+        android:background="@drawable/keyboard_quick_switch_text_button_background"
+        android:backgroundTint="?androidprv:attr/materialColorSurfaceContainer"
+        android:paddingHorizontal="@dimen/keyboard_quick_switch_text_button_horizontal_padding"
 
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintBottom_toBottomOf="parent"
@@ -37,10 +40,11 @@
 
         <ImageView
             android:id="@+id/icon"
-            android:layout_width="@dimen/keyboard_quick_switch_recents_icon_size"
-            android:layout_height="@dimen/keyboard_quick_switch_recents_icon_size"
-            android:layout_marginBottom="8dp"
-            android:tint="?attr/materialColorOnSurface"
+            android:layout_width="@dimen/keyboard_quick_switch_desktop_icon_size"
+            android:layout_height="@dimen/keyboard_quick_switch_desktop_icon_size"
+            android:layout_marginBottom="4dp"
+            android:tint="?androidprv:attr/materialColorOnSurface"
+            android:src="@drawable/ic_desktop"
 
             app:layout_constraintVertical_chainStyle="packed"
             app:layout_constraintTop_toTopOf="parent"
@@ -49,9 +53,9 @@
             app:layout_constraintEnd_toEndOf="parent"/>
 
         <TextView
-            style="@style/KeyboardQuickSwitchText"
+            style="@style/KeyboardQuickSwitchText.OnTaskView"
             android:id="@+id/text"
-            android:layout_width="wrap_content"
+            android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:textAlignment="center"
 
diff --git a/quickstep/res/layout/keyboard_quick_switch_textonly_taskview.xml b/quickstep/res/layout/keyboard_quick_switch_overview_taskview.xml
similarity index 62%
copy from quickstep/res/layout/keyboard_quick_switch_textonly_taskview.xml
copy to quickstep/res/layout/keyboard_quick_switch_overview_taskview.xml
index c76a2e3..0b44a2a 100644
--- a/quickstep/res/layout/keyboard_quick_switch_textonly_taskview.xml
+++ b/quickstep/res/layout/keyboard_quick_switch_overview_taskview.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2023 The Android Open Source Project
+<!-- 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.
@@ -18,44 +18,47 @@
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:launcher="http://schemas.android.com/apk/res-auto"
-    android:layout_width="@dimen/keyboard_quick_switch_taskview_width"
-    android:layout_height="@dimen/keyboard_quick_switch_taskview_height"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
     android:clipToOutline="true"
     android:importantForAccessibility="yes"
-    launcher:focusBorderColor="?attr/materialColorOutline">
+    launcher:focusBorderColor="?androidprv:attr/materialColorOutline"
+    launcher:focusBorderRadius="@dimen/keyboard_quick_switch_text_button_radius">
 
     <androidx.constraintlayout.widget.ConstraintLayout
         android:id="@+id/content"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:background="@drawable/keyboard_quick_switch_overview_button_background"
+        android:layout_width="@dimen/keyboard_quick_switch_text_button_width"
+        android:layout_height="@dimen/keyboard_quick_switch_taskview_height"
+        android:background="@drawable/keyboard_quick_switch_text_button_background"
+        android:backgroundTint="?androidprv:attr/materialColorSurfaceBright"
+        android:paddingHorizontal="@dimen/keyboard_quick_switch_text_button_horizontal_padding"
 
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintEnd_toEndOf="parent">
 
-        <ImageView
-            android:id="@+id/icon"
-            android:layout_width="@dimen/keyboard_quick_switch_recents_icon_size"
-            android:layout_height="@dimen/keyboard_quick_switch_recents_icon_size"
-            android:layout_marginBottom="8dp"
-            android:tint="?attr/materialColorOnSurface"
-
-            app:layout_constraintVertical_chainStyle="packed"
-            app:layout_constraintTop_toTopOf="parent"
-            app:layout_constraintBottom_toTopOf="@id/text"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintEnd_toEndOf="parent"/>
-
         <TextView
-            style="@style/KeyboardQuickSwitchText"
-            android:id="@+id/text"
+            style="@style/KeyboardQuickSwitchText.LargeText"
+            android:id="@+id/large_text"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:textAlignment="center"
 
-            app:layout_constraintTop_toBottomOf="@id/icon"
+            app:layout_constraintVertical_chainStyle="packed"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintBottom_toTopOf="@id/small_text"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"/>
+
+        <TextView
+            style="@style/KeyboardQuickSwitchText.OnTaskView"
+            android:id="@+id/small_text"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:textAlignment="center"
+
+            app:layout_constraintTop_toBottomOf="@id/large_text"
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintStart_toStartOf="parent"
             app:layout_constraintEnd_toEndOf="parent"/>
diff --git a/quickstep/res/layout/keyboard_quick_switch_taskview.xml b/quickstep/res/layout/keyboard_quick_switch_taskview.xml
index 8f09176..41eb623 100644
--- a/quickstep/res/layout/keyboard_quick_switch_taskview.xml
+++ b/quickstep/res/layout/keyboard_quick_switch_taskview.xml
@@ -18,8 +18,8 @@
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:launcher="http://schemas.android.com/apk/res-auto"
-    android:layout_width="@dimen/keyboard_quick_switch_taskview_width"
-    android:layout_height="@dimen/keyboard_quick_switch_taskview_height"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
     android:importantForAccessibility="yes"
     android:background="@drawable/keyboard_quick_switch_task_view_background"
     android:clipToOutline="true"
@@ -27,8 +27,8 @@
 
     <androidx.constraintlayout.widget.ConstraintLayout
         android:id="@+id/content"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
+        android:layout_width="@dimen/keyboard_quick_switch_taskview_width"
+        android:layout_height="@dimen/keyboard_quick_switch_taskview_height"
 
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintBottom_toBottomOf="parent"
@@ -52,7 +52,7 @@
             android:layout_width="match_parent"
             android:layout_height="0dp"
             android:visibility="gone"
-            android:layout_marginTop="@dimen/keyboard_quick_switch_split_view_spacing"
+            android:layout_marginTop="@dimen/keyboard_quick_switch_view_small_spacing"
 
             app:layout_constraintTop_toBottomOf="@id/thumbnail_1"
             app:layout_constraintBottom_toBottomOf="parent"
diff --git a/quickstep/res/layout/keyboard_quick_switch_taskview_square.xml b/quickstep/res/layout/keyboard_quick_switch_taskview_square.xml
new file mode 100644
index 0000000..1474949
--- /dev/null
+++ b/quickstep/res/layout/keyboard_quick_switch_taskview_square.xml
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<com.android.launcher3.taskbar.KeyboardQuickSwitchTaskView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:importantForAccessibility="yes"
+    android:background="@drawable/keyboard_quick_switch_task_view_background"
+    android:clipToOutline="true"
+    launcher:focusBorderColor="?androidprv:attr/materialColorOutline">
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/content"
+        android:layout_width="0dp"
+        android:layout_height="@dimen/keyboard_quick_switch_taskview_height"
+
+        app:layout_constraintDimensionRatio="1:1"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent">
+
+        <include
+            layout="@layout/keyboard_quick_switch_taskview_thumbnail"
+            android:id="@+id/thumbnail_1"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintEnd_toStartOf="@id/thumbnail_2"/>
+
+        <include
+            layout="@layout/keyboard_quick_switch_taskview_thumbnail"
+            android:id="@+id/thumbnail_2"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:visibility="gone"
+            android:layout_marginStart="@dimen/keyboard_quick_switch_view_small_spacing"
+
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintStart_toEndOf="@id/thumbnail_1"
+            app:layout_constraintEnd_toEndOf="parent"/>
+
+        <ImageView
+            android:id="@+id/icon_1"
+            android:layout_width="@dimen/keyboard_quick_switch_taskview_icon_size"
+            android:layout_height="@dimen/keyboard_quick_switch_taskview_icon_size"
+            android:importantForAccessibility="no"
+            android:scaleType="centerCrop"
+
+            app:layout_constraintTop_toTopOf="@id/thumbnail_1"
+            app:layout_constraintBottom_toBottomOf="@id/thumbnail_1"
+            app:layout_constraintStart_toStartOf="@id/thumbnail_1"
+            app:layout_constraintEnd_toEndOf="@id/thumbnail_1"/>
+
+        <ImageView
+            android:id="@+id/icon_2"
+            android:layout_width="@dimen/keyboard_quick_switch_taskview_icon_size"
+            android:layout_height="@dimen/keyboard_quick_switch_taskview_icon_size"
+            android:importantForAccessibility="no"
+            android:visibility="gone"
+            android:scaleType="centerCrop"
+
+            app:layout_constraintTop_toTopOf="@id/thumbnail_2"
+            app:layout_constraintBottom_toBottomOf="@id/thumbnail_2"
+            app:layout_constraintStart_toStartOf="@id/thumbnail_2"
+            app:layout_constraintEnd_toEndOf="@id/thumbnail_2"/>
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+</com.android.launcher3.taskbar.KeyboardQuickSwitchTaskView>
diff --git a/quickstep/res/values-land/dimens.xml b/quickstep/res/values-land/dimens.xml
index e862b9e..2239f8b 100644
--- a/quickstep/res/values-land/dimens.xml
+++ b/quickstep/res/values-land/dimens.xml
@@ -83,7 +83,7 @@
     <dimen name="taskbar_suw_frame">96dp</dimen>
     <dimen name="taskbar_suw_insets">24dp</dimen>
 
-    <dimen name="keyboard_quick_switch_taskview_width">205dp</dimen>
-    <dimen name="keyboard_quick_switch_taskview_height">119dp</dimen>
+    <!-- Keyboard Quick Switch -->
+    <dimen name="keyboard_quick_switch_taskview_width">217.6dp</dimen>
 
 </resources>
diff --git a/quickstep/res/values-night/styles.xml b/quickstep/res/values-night/styles.xml
index 2cb633a..eb88310 100644
--- a/quickstep/res/values-night/styles.xml
+++ b/quickstep/res/values-night/styles.xml
@@ -14,16 +14,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<resources
-    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
-
-    <style name="AllSetTheme" parent="@android:style/Theme.DeviceDefault.NoActionBar">
-        <item name="android:navigationBarColor">@android:color/transparent</item>
-        <item name="android:statusBarColor">@android:color/transparent</item>
-        <item name="android:enforceNavigationBarContrast">false</item>
-        <item name="android:windowLightStatusBar">false</item>
-        <item name="android:windowBackground">@android:color/transparent</item>
-    </style>
+<resources>
 
     <style name="TextAppearance.GestureTutorial.MainTitle.Home"
         parent="TextAppearance.GestureTutorial.MainTitle">
diff --git a/quickstep/res/values/attrs.xml b/quickstep/res/values/attrs.xml
index ccc7f18..7fd6b5c 100644
--- a/quickstep/res/values/attrs.xml
+++ b/quickstep/res/values/attrs.xml
@@ -28,6 +28,7 @@
         <!-- Border color for a keyboard quick switch task views -->
         <attr name="focusBorderColor" format="color" />
         <attr name="hoverBorderColor" format="color" />
+        <attr name="focusBorderRadius" format="dimension" />
     </declare-styleable>
 
     <declare-styleable name="ClearAllButton">
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index d981882..ce3f3ac 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -437,6 +437,7 @@
     <dimen name="bubblebar_stashed_handle_width">55dp</dimen>
     <dimen name="bubblebar_stashed_size">@dimen/transient_taskbar_stashed_height</dimen>
     <dimen name="bubblebar_stashed_handle_height">@dimen/taskbar_stashed_handle_height</dimen>
+    <dimen name="bubblebar_stashed_handle_spring_velocity_dp_per_s">@dimen/transient_taskbar_stash_spring_velocity_dp_per_s</dimen>
     <!-- this is a pointer height minus 1dp to ensure the pointer overlaps with the bubblebar
     background appropriately when close to the rounded corner -->
     <dimen name="bubblebar_pointer_visible_size">9dp</dimen>
@@ -487,17 +488,22 @@
     <!-- Keyboard Quick Switch -->
     <dimen name="keyboard_quick_switch_border_width">4dp</dimen>
     <dimen name="keyboard_quick_switch_taskview_width">104dp</dimen>
-    <dimen name="keyboard_quick_switch_taskview_height">134dp</dimen>
+    <dimen name="keyboard_quick_switch_taskview_height">136dp</dimen>
     <dimen name="keyboard_quick_switch_taskview_icon_size">52dp</dimen>
     <dimen name="keyboard_quick_switch_recents_icon_size">20dp</dimen>
+    <dimen name="keyboard_quick_switch_desktop_icon_size">32dp</dimen>
     <dimen name="keyboard_quick_switch_margin_top">56dp</dimen>
     <dimen name="keyboard_quick_switch_margin_ends">16dp</dimen>
     <dimen name="keyboard_quick_switch_view_spacing">16dp</dimen>
-    <dimen name="keyboard_quick_switch_split_view_spacing">2dp</dimen>
+    <dimen name="keyboard_quick_switch_view_small_spacing">4dp</dimen>
     <dimen name="keyboard_quick_switch_view_radius">28dp</dimen>
     <dimen name="keyboard_quick_switch_task_view_radius">16dp</dimen>
     <dimen name="keyboard_quick_switch_no_recent_items_icon_size">24dp</dimen>
     <dimen name="keyboard_quick_switch_no_recent_items_icon_margin">8dp</dimen>
+    <dimen name="keyboard_quick_switch_text_button_width">104dp</dimen>
+    <dimen name="keyboard_quick_switch_text_button_radius">360dp</dimen>
+    <dimen name="keyboard_quick_switch_text_button_horizontal_padding">16dp</dimen>
+    <dimen name="keyboard_quick_switch_text_button_fade_edge_length">20dp</dimen>
 
     <!-- Digital Wellbeing -->
     <dimen name="digital_wellbeing_toast_height">48dp</dimen>
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index d0f474f..f72f3c5 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -327,17 +327,14 @@
     <!-- Label for moving drop target to the bottom or right side of the screen, depending on orientation (from the Taskbar only). -->
     <string name="move_drop_target_bottom_or_right">Move to bottom&#47;right</string>
 
-    <!-- Label for quick switch tile showing how many more apps are available [CHAR LIMIT=NONE] -->
+    <!-- Label for quick switch tile showing how many more apps are available. The number will be displayed above this text. [CHAR LIMIT=NONE] -->
     <string name="quick_switch_overflow">{count, plural,
-            =1{Show # more app.}
-            other{Show # more apps.}
+            =1{more app}
+            other{more apps}
         }</string>
 
-    <!-- Label for quick switch tile showing how many apps are available in desktop mode [CHAR LIMIT=NONE] -->
-    <string name="quick_switch_desktop">{count, plural,
-            =1{Show # desktop app.}
-            other{Show # desktop apps.}
-        }</string>
+    <!-- Label for quick switch tile to launch desktop mode [CHAR LIMIT=NONE] -->
+    <string name="quick_switch_desktop">Desktop</string>
 
     <!-- Accessibility label for quick switch tiles showing split tasks [CHAR LIMIT=NONE] -->
     <string name="quick_switch_split_task"><xliff:g id="app_name_1" example="Chrome">%1$s</xliff:g> and <xliff:g id="app_name_2" example="Gmail">%2$s</xliff:g></string>
diff --git a/quickstep/res/values/styles.xml b/quickstep/res/values/styles.xml
index 1f4720c..c423d09 100644
--- a/quickstep/res/values/styles.xml
+++ b/quickstep/res/values/styles.xml
@@ -205,7 +205,7 @@
         <item name="android:textColor">?attr/tutorialSubtitle</item>
     </style>
 
-    <style name="AllSetTheme" parent="@android:style/Theme.DeviceDefault.Light.NoActionBar">
+    <style name="AllSetTheme" parent="@style/DynamicColorsBaseLauncherTheme.NoActionBar">
         <item name="android:navigationBarColor">@android:color/transparent</item>
         <item name="android:statusBarColor">@android:color/transparent</item>
         <item name="android:enforceNavigationBarContrast">false</item>
@@ -272,10 +272,22 @@
         <item name="lineHeight">20sp</item>
     </style>
 
+    <style name="KeyboardQuickSwitchText.LargeText" parent="KeyboardQuickSwitchText">
+        <item name="android:textSize">32sp</item>
+    </style>
+
     <style name="KeyboardQuickSwitchText.OnBackground" parent="KeyboardQuickSwitchText">
         <item name="android:textColor">?attr/materialColorOnSurface</item>
     </style>
 
+    <style name="KeyboardQuickSwitchText.OnTaskView" parent="KeyboardQuickSwitchText">
+        <item name="android:requiresFadingEdge">horizontal</item>
+        <item name="android:fadeScrollbars">false</item>
+        <item name="android:fadingEdgeLength">20dp</item>
+        <item name="android:ellipsize">none</item>
+        <item name="android:singleLine">true</item>
+    </style>
+
     <style name="GestureTutorialActivity" parent="@style/AppTheme">
         <item name="background">@android:color/transparent</item>
         <item name="tutorialSubtitle">@android:color/black</item>
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 4a1035f..e51c956 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -65,7 +65,6 @@
 import static com.android.launcher3.util.window.RefreshRateTracker.getSingleFrameMs;
 import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
 import static com.android.launcher3.views.FloatingIconView.getFloatingIconView;
-import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
 import static com.android.quickstep.TaskViewUtils.findTaskViewToLaunch;
 import static com.android.quickstep.util.AnimUtils.clampToDuration;
 import static com.android.quickstep.util.AnimUtils.completeRunnableListCallback;
@@ -1228,9 +1227,7 @@
      * Registers remote animations used when closing apps to home screen.
      */
     public void registerRemoteTransitions() {
-        if (ENABLE_SHELL_TRANSITIONS) {
-            SystemUiProxy.INSTANCE.get(mLauncher).shareTransactionQueue();
-        }
+        SystemUiProxy.INSTANCE.get(mLauncher).shareTransactionQueue();
         if (SEPARATE_RECENTS_ACTIVITY.get()) {
             return;
         }
@@ -1294,9 +1291,7 @@
     }
 
     protected void unregisterRemoteTransitions() {
-        if (ENABLE_SHELL_TRANSITIONS) {
-            SystemUiProxy.INSTANCE.get(mLauncher).unshareTransactionQueue();
-        }
+        SystemUiProxy.INSTANCE.get(mLauncher).unshareTransactionQueue();
         if (SEPARATE_RECENTS_ACTIVITY.get()) {
             return;
         }
diff --git a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
index e925af6..50e8e5e 100644
--- a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
+++ b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
@@ -20,7 +20,6 @@
 import static android.view.WindowInsets.Type.navigationBars;
 import static android.view.WindowInsets.Type.statusBars;
 
-import static com.android.launcher3.Flags.enablePredictiveBackGesture;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
@@ -170,11 +169,6 @@
 
     @Override
     protected void registerBackDispatcher() {
-        if (!enablePredictiveBackGesture()) {
-            super.registerBackDispatcher();
-            return;
-        }
-
         getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
                 OnBackInvokedDispatcher.PRIORITY_DEFAULT,
                 new BackAnimationCallback());
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
index 46501c4..e4cc6bb 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
@@ -29,6 +29,7 @@
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.util.DesktopTask;
 import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.util.LayoutUtils;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -66,6 +67,9 @@
 
     @Nullable private KeyboardQuickSwitchViewController mQuickSwitchViewController;
 
+    private boolean mHasDesktopTask = false;
+    private boolean mWasDesktopTaskFilteredOut = false;
+
     /** Initialize the controller. */
     public void init(@NonNull TaskbarControllers controllers) {
         mControllers = controllers;
@@ -126,11 +130,15 @@
                     /* updateTasks= */ false,
                     currentFocusedIndex == -1 && !mControllerCallbacks.isFirstTaskRunning()
                             ? 0 : currentFocusedIndex,
-                    onDesktop);
+                    onDesktop,
+                    mHasDesktopTask,
+                    mWasDesktopTaskFilteredOut);
             return;
         }
 
         mTaskListChangeId = mModel.getTasks((tasks) -> {
+            mHasDesktopTask = false;
+            mWasDesktopTaskFilteredOut = false;
             if (onDesktop) {
                 processLoadedTasksOnDesktop(tasks);
             } else {
@@ -144,7 +152,9 @@
                     /* updateTasks= */ true,
                     currentFocusedIndex == -1 && !mControllerCallbacks.isFirstTaskRunning()
                             ? 0 : currentFocusedIndex,
-                    onDesktop);
+                    onDesktop,
+                    mHasDesktopTask,
+                    mWasDesktopTaskFilteredOut);
         });
     }
 
@@ -152,9 +162,22 @@
         // Only store MAX_TASK tasks, from most to least recent
         Collections.reverse(tasks);
         mTasks = tasks.stream()
+                .filter(task -> !(task instanceof DesktopTask))
                 .limit(MAX_TASKS)
                 .collect(Collectors.toList());
-        mNumHiddenTasks = Math.max(0, tasks.size() - MAX_TASKS);
+
+        for (int i = 0; i < tasks.size(); i++) {
+            if (tasks.get(i) instanceof DesktopTask) {
+                mHasDesktopTask = true;
+                if (i < mTasks.size()) {
+                    mWasDesktopTaskFilteredOut = true;
+                }
+                break;
+            }
+        }
+
+        mNumHiddenTasks = Math.max(0,
+                tasks.size() - (mWasDesktopTaskFilteredOut ? 1 : 0) - MAX_TASKS);
     }
 
     private void processLoadedTasksOnDesktop(List<GroupTask> tasks) {
@@ -214,6 +237,8 @@
         pw.println(prefix + "\tisOpen=" + (mQuickSwitchViewController != null));
         pw.println(prefix + "\tmNumHiddenTasks=" + mNumHiddenTasks);
         pw.println(prefix + "\tmTaskListChangeId=" + mTaskListChangeId);
+        pw.println(prefix + "\tmHasDesktopTask=" + mHasDesktopTask);
+        pw.println(prefix + "\tmWasDesktopTaskFilteredOut=" + mWasDesktopTaskFilteredOut);
         pw.println(prefix + "\tmTasks=[");
         for (GroupTask task : mTasks) {
             Task task1 = task.task1;
@@ -235,10 +260,6 @@
 
     class ControllerCallbacks {
 
-        int getTaskCount() {
-            return mTasks.size() + (mNumHiddenTasks == 0 ? 0 : 1);
-        }
-
         @Nullable
         GroupTask getTaskAt(int index) {
             return index < 0 || index >= mTasks.size() ? null : mTasks.get(index);
@@ -279,5 +300,10 @@
         boolean isFirstTaskRunning() {
             return isTaskRunning(getTaskAt(0));
         }
+
+        boolean isAspectRatioSquare() {
+            return mControllers != null && LayoutUtils.isAspectRatioSquare(
+                    mControllers.taskbarActivityContext.getDeviceProfile().aspectRatio);
+        }
     }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java
index 39b4f77..8ceb77d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java
@@ -50,8 +50,10 @@
 public class KeyboardQuickSwitchTaskView extends ConstraintLayout {
 
     private static final float THUMBNAIL_BLUR_RADIUS = 1f;
+    private static final int INVALID_BORDER_RADIUS = -1;
 
     @ColorInt private final int mBorderColor;
+    @ColorInt private final int mBorderRadius;
 
     @Nullable private BorderAnimator mBorderAnimator;
 
@@ -87,6 +89,8 @@
 
         mBorderColor = ta.getColor(
                 R.styleable.TaskView_focusBorderColor, DEFAULT_BORDER_COLOR);
+        mBorderRadius = ta.getDimensionPixelSize(
+                R.styleable.TaskView_focusBorderRadius, INVALID_BORDER_RADIUS);
         ta.recycle();
     }
 
@@ -103,8 +107,10 @@
 
         Preconditions.assertNotNull(mContent);
         mBorderAnimator = BorderAnimator.createScalingBorderAnimator(
-                /* borderRadiusPx= */ resources.getDimensionPixelSize(
-                        R.dimen.keyboard_quick_switch_task_view_radius),
+                /* borderRadiusPx= */ mBorderRadius != INVALID_BORDER_RADIUS
+                        ? mBorderRadius
+                        : resources.getDimensionPixelSize(
+                                R.dimen.keyboard_quick_switch_task_view_radius),
                 /* borderWidthPx= */ resources.getDimensionPixelSize(
                                 R.dimen.keyboard_quick_switch_border_width),
                 /* boundsBuilder= */ bounds -> {
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
index dbd9c73..a527c82 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
@@ -36,14 +36,12 @@
 import android.view.ViewTreeObserver;
 import android.view.animation.Interpolator;
 import android.widget.HorizontalScrollView;
-import android.widget.ImageView;
 import android.widget.TextView;
 
 import androidx.annotation.LayoutRes;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.constraintlayout.widget.ConstraintLayout;
-import androidx.core.content.res.ResourcesCompat;
 
 import com.android.app.animation.Interpolators;
 import com.android.internal.jank.Cuj;
@@ -52,9 +50,7 @@
 import com.android.launcher3.anim.AnimatedFloat;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.shared.TestProtocol;
-import com.android.quickstep.util.DesktopTask;
 import com.android.quickstep.util.GroupTask;
-import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
 
 import java.util.HashMap;
@@ -102,9 +98,13 @@
     private int mTaskViewWidth;
     private int mTaskViewHeight;
     private int mSpacing;
+    private int mSmallSpacing;
     private int mOutlineRadius;
     private boolean mIsRtl;
 
+    private int mOverviewTaskIndex = -1;
+    private int mDesktopTaskIndex = -1;
+
     @Nullable private AnimatorSet mOpenAnimation;
 
     @Nullable private KeyboardQuickSwitchViewController.ViewCallbacks mViewCallbacks;
@@ -141,6 +141,8 @@
         mTaskViewHeight = resources.getDimensionPixelSize(
                 R.dimen.keyboard_quick_switch_taskview_height);
         mSpacing = resources.getDimensionPixelSize(R.dimen.keyboard_quick_switch_view_spacing);
+        mSmallSpacing = resources.getDimensionPixelSize(
+                R.dimen.keyboard_quick_switch_view_small_spacing);
         mOutlineRadius = resources.getDimensionPixelSize(R.dimen.keyboard_quick_switch_view_radius);
         mIsRtl = Utilities.isRtl(resources);
     }
@@ -148,6 +150,7 @@
     private KeyboardQuickSwitchTaskView createAndAddTaskView(
             int index,
             boolean isFinalView,
+            boolean useSmallStartSpacing,
             @LayoutRes int resId,
             @NonNull LayoutInflater layoutInflater,
             @Nullable View previousView) {
@@ -156,7 +159,7 @@
         taskView.setId(View.generateViewId());
         taskView.setOnClickListener(v -> mViewCallbacks.launchTaskAt(index));
 
-        LayoutParams lp = new LayoutParams(mTaskViewWidth, mTaskViewHeight);
+        LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
         // Create a left-to-right ordering of views (or right-to-left in RTL locales)
         if (previousView != null) {
             lp.startToEnd = previousView.getId();
@@ -166,7 +169,7 @@
         lp.topToTop = PARENT_ID;
         lp.bottomToBottom = PARENT_ID;
         // Add spacing between views
-        lp.setMarginStart(mSpacing);
+        lp.setMarginStart(useSmallStartSpacing ? mSmallSpacing : mSpacing);
         if (isFinalView) {
             // Add spacing to the end of the final view so that scrolling ends with some padding.
             lp.endToEnd = PARENT_ID;
@@ -185,7 +188,8 @@
             int numHiddenTasks,
             boolean updateTasks,
             int currentFocusIndexOverride,
-            @NonNull KeyboardQuickSwitchViewController.ViewCallbacks viewCallbacks) {
+            @NonNull KeyboardQuickSwitchViewController.ViewCallbacks viewCallbacks,
+            boolean useDesktopTaskView) {
         mViewCallbacks = viewCallbacks;
         Resources resources = context.getResources();
         Resources.Theme theme = context.getTheme();
@@ -197,57 +201,62 @@
             GroupTask groupTask = groupTasks.get(i);
             KeyboardQuickSwitchTaskView currentTaskView = createAndAddTaskView(
                     i,
-                    /* isFinalView= */ i == tasksToDisplay - 1 && numHiddenTasks == 0,
-                    groupTask instanceof DesktopTask
-                            ? R.layout.keyboard_quick_switch_textonly_taskview
+                    /* isFinalView= */ i == tasksToDisplay - 1
+                            && numHiddenTasks == 0 && !useDesktopTaskView,
+                    /* useSmallStartSpacing= */ false,
+                    mViewCallbacks.isAspectRatioSquare()
+                            ? R.layout.keyboard_quick_switch_taskview_square
                             : R.layout.keyboard_quick_switch_taskview,
                     layoutInflater,
                     previousTaskView);
 
-            if (groupTask instanceof DesktopTask desktopTask) {
-                HashMap<String, Integer> args = new HashMap<>();
-                args.put("count", desktopTask.tasks.size());
+            final boolean firstTaskIsLeftTopTask =
+                    groupTask.mSplitBounds == null
+                            || groupTask.mSplitBounds.leftTopTaskId == groupTask.task1.key.id
+                            || groupTask.task2 == null;
+            currentTaskView.setThumbnails(
+                    firstTaskIsLeftTopTask ? groupTask.task1 : groupTask.task2,
+                    firstTaskIsLeftTopTask ? groupTask.task2 : groupTask.task1,
+                    updateTasks ? mViewCallbacks::updateThumbnailInBackground : null,
+                    updateTasks ? mViewCallbacks::updateIconInBackground : null);
 
-                currentTaskView.<ImageView>findViewById(R.id.icon).setImageDrawable(
-                        ResourcesCompat.getDrawable(resources, R.drawable.ic_desktop, theme));
-                currentTaskView.<TextView>findViewById(R.id.text).setText(new MessageFormat(
-                        resources.getString(R.string.quick_switch_desktop),
-                        Locale.getDefault()).format(args));
-            } else {
-                final boolean firstTaskIsLeftTopTask =
-                        groupTask.mSplitBounds == null
-                        || groupTask.mSplitBounds.leftTopTaskId == groupTask.task1.key.id;
-                final Task leftTopTask = firstTaskIsLeftTopTask
-                        ? groupTask.task1 : groupTask.task2;
-                final Task rightBottomTask = firstTaskIsLeftTopTask
-                        ? groupTask.task2 : groupTask.task1;
-                currentTaskView.setThumbnails(
-                        leftTopTask,
-                        rightBottomTask,
-                        updateTasks ? mViewCallbacks::updateThumbnailInBackground : null,
-                        updateTasks ? mViewCallbacks::updateIconInBackground : null);
-            }
             previousTaskView = currentTaskView;
         }
-
         if (numHiddenTasks > 0) {
             HashMap<String, Integer> args = new HashMap<>();
             args.put("count", numHiddenTasks);
 
+            mOverviewTaskIndex = getTaskCount();
             View overviewButton = createAndAddTaskView(
-                    MAX_TASKS,
-                    /* isFinalView= */ true,
-                    R.layout.keyboard_quick_switch_textonly_taskview,
+                    mOverviewTaskIndex,
+                    /* isFinalView= */ !useDesktopTaskView,
+                    /* useSmallStartSpacing= */ false,
+                    R.layout.keyboard_quick_switch_overview_taskview,
                     layoutInflater,
                     previousTaskView);
 
-            overviewButton.<ImageView>findViewById(R.id.icon).setImageDrawable(
-                    ResourcesCompat.getDrawable(resources, R.drawable.view_carousel, theme));
-            overviewButton.<TextView>findViewById(R.id.text).setText(new MessageFormat(
+            overviewButton.<TextView>findViewById(R.id.large_text).setText(
+                    String.format(Locale.getDefault(), "%d", numHiddenTasks));
+            overviewButton.<TextView>findViewById(R.id.small_text).setText(new MessageFormat(
                     resources.getString(R.string.quick_switch_overflow),
                     Locale.getDefault()).format(args));
+
+            previousTaskView = overviewButton;
         }
-        mDisplayingRecentTasks = !groupTasks.isEmpty();
+        if (useDesktopTaskView) {
+            mDesktopTaskIndex = getTaskCount();
+            View desktopButton = createAndAddTaskView(
+                    mDesktopTaskIndex,
+                    /* isFinalView= */ true,
+                    /* useSmallStartSpacing= */ numHiddenTasks > 0,
+                    R.layout.keyboard_quick_switch_desktop_taskview,
+                    layoutInflater,
+                    previousTaskView);
+
+            desktopButton.<TextView>findViewById(R.id.text).setText(
+                    resources.getString(R.string.quick_switch_desktop));
+        }
+        mDisplayingRecentTasks = !groupTasks.isEmpty() || useDesktopTaskView;
 
         getViewTreeObserver().addOnGlobalLayoutListener(
                 new ViewTreeObserver.OnGlobalLayoutListener() {
@@ -260,6 +269,14 @@
                 });
     }
 
+    int getOverviewTaskIndex() {
+        return mOverviewTaskIndex;
+    }
+
+    int getDesktopTaskIndex() {
+        return mDesktopTaskIndex;
+    }
+
     protected Animator getCloseAnimation() {
         AnimatorSet closeAnimation = new AnimatorSet();
 
@@ -370,7 +387,7 @@
                     }
                 });
                 animateFocusMove(-1, Math.min(
-                        mContent.getChildCount() - 1,
+                        getTaskCount() - 1,
                         currentFocusIndexOverride == -1 ? 1 : currentFocusIndexOverride));
                 displayedContent.setVisibility(VISIBLE);
                 setVisibility(VISIBLE);
@@ -577,7 +594,11 @@
 
     @Nullable
     protected KeyboardQuickSwitchTaskView getTaskAt(int index) {
-        return !mDisplayingRecentTasks || index < 0 || index >= mContent.getChildCount()
+        return !mDisplayingRecentTasks || index < 0 || index >= getTaskCount()
                 ? null : (KeyboardQuickSwitchTaskView) mContent.getChildAt(index);
     }
+
+    public int getTaskCount() {
+        return mContent.getChildCount();
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
index d411ba6..40e77e2 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3.taskbar;
 
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.view.KeyEvent;
@@ -28,6 +30,7 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatorListeners;
 import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
+import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.util.GroupTask;
 import com.android.quickstep.util.SlideInRemoteTransition;
 import com.android.systemui.shared.recents.model.Task;
@@ -56,6 +59,7 @@
     private int mCurrentFocusIndex = -1;
 
     private boolean mOnDesktop;
+    private boolean mWasDesktopTaskFilteredOut;
 
     protected KeyboardQuickSwitchViewController(
             @NonNull TaskbarControllers controllers,
@@ -77,9 +81,12 @@
             int numHiddenTasks,
             boolean updateTasks,
             int currentFocusIndexOverride,
-            boolean onDesktop) {
+            boolean onDesktop,
+            boolean hasDesktopTask,
+            boolean wasDesktopTaskFilteredOut) {
         mOverlayContext.getDragLayer().addView(mKeyboardQuickSwitchView);
         mOnDesktop = onDesktop;
+        mWasDesktopTaskFilteredOut = wasDesktopTaskFilteredOut;
 
         mKeyboardQuickSwitchView.applyLoadPlan(
                 mOverlayContext,
@@ -87,7 +94,8 @@
                 numHiddenTasks,
                 updateTasks,
                 currentFocusIndexOverride,
-                mViewCallbacks);
+                mViewCallbacks,
+                /* useDesktopTaskView= */ !onDesktop && hasDesktopTask);
     }
 
     boolean isCloseAnimationRunning() {
@@ -136,7 +144,7 @@
         }
         // If the user quick switches too quickly, updateCurrentFocusIndex might not have run.
         return launchTaskAt(mControllerCallbacks.isFirstTaskRunning()
-                && mControllerCallbacks.getTaskCount() > 1 ? 1 : 0);
+                && mKeyboardQuickSwitchView.getTaskCount() > 1 ? 1 : 0);
     }
 
     private int launchTaskAt(int index) {
@@ -144,17 +152,11 @@
             // Ignore taps on task views and alt key unpresses while the close animation is running.
             return -1;
         }
-        // Even with a valid index, this can be null if the user tries to quick switch before the
-        // views have been added in the KeyboardQuickSwitchView.
-        GroupTask task = mControllerCallbacks.getTaskAt(index);
-        if (task == null) {
-            return mOnDesktop ? 1 : Math.max(0, index);
+        if (index == mKeyboardQuickSwitchView.getOverviewTaskIndex()) {
+            // If there is a desktop task view, then we should account for it when focusing the
+            // first hidden non-desktop task view in recents view
+            return mOnDesktop ? 1 : (mWasDesktopTaskFilteredOut ? index + 1 : index);
         }
-        if (mControllerCallbacks.isTaskRunning(task)) {
-            // Ignore attempts to run the selected task if it is already running.
-            return -1;
-        }
-
         Runnable onStartCallback = () -> InteractionJankMonitorWrapper.begin(
                 mKeyboardQuickSwitchView, Cuj.CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH);
         Runnable onFinishCallback = () -> InteractionJankMonitorWrapper.end(
@@ -169,6 +171,24 @@
                 onStartCallback,
                 onFinishCallback),
                 "SlideInTransition");
+        if (index == mKeyboardQuickSwitchView.getDesktopTaskIndex()) {
+            UI_HELPER_EXECUTOR.execute(() ->
+                    SystemUiProxy.INSTANCE.get(mKeyboardQuickSwitchView.getContext())
+                            .showDesktopApps(
+                                    mKeyboardQuickSwitchView.getDisplay().getDisplayId(),
+                                    remoteTransition));
+            return -1;
+        }
+        // Even with a valid index, this can be null if the user tries to quick switch before the
+        // views have been added in the KeyboardQuickSwitchView.
+        GroupTask task = mControllerCallbacks.getTaskAt(index);
+        if (task == null) {
+            return mOnDesktop ? 1 : Math.max(0, index);
+        }
+        if (mControllerCallbacks.isTaskRunning(task)) {
+            // Ignore attempts to run the selected task if it is already running.
+            return -1;
+        }
         mControllers.taskbarActivityContext.handleGroupTaskLaunch(
                 task,
                 remoteTransition,
@@ -195,6 +215,8 @@
         pw.println(prefix + "\thasFocus=" + mKeyboardQuickSwitchView.hasFocus());
         pw.println(prefix + "\tisCloseAnimationRunning=" + isCloseAnimationRunning());
         pw.println(prefix + "\tmCurrentFocusIndex=" + mCurrentFocusIndex);
+        pw.println(prefix + "\tmOnDesktop=" + mOnDesktop);
+        pw.println(prefix + "\tmWasDesktopTaskFilteredOut=" + mWasDesktopTaskFilteredOut);
     }
 
     class ViewCallbacks {
@@ -222,7 +244,7 @@
             boolean traverseBackwards = (keyCode == KeyEvent.KEYCODE_TAB && event.isShiftPressed())
                     || (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT && isRTL)
                     || (keyCode == KeyEvent.KEYCODE_DPAD_LEFT && !isRTL);
-            int taskCount = mControllerCallbacks.getTaskCount();
+            int taskCount = mKeyboardQuickSwitchView.getTaskCount();
             int toIndex = mCurrentFocusIndex == -1
                     // Focus the second-most recent app if possible
                     ? (taskCount > 1 ? 1 : 0)
@@ -257,5 +279,9 @@
         void updateIconInBackground(Task task, Consumer<Task> callback) {
             mControllerCallbacks.updateIconInBackground(task, callback);
         }
+
+        boolean isAspectRatioSquare() {
+            return mControllerCallbacks.isAspectRatioSquare();
+        }
     }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 96a6d28..69da7b6 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -19,7 +19,6 @@
 import static com.android.launcher3.statemanager.BaseState.FLAG_NON_INTERACTIVE;
 import static com.android.launcher3.taskbar.TaskbarEduTooltipControllerKt.TOOLTIP_STEP_FEATURES;
 import static com.android.launcher3.taskbar.TaskbarLauncherStateController.FLAG_VISIBLE;
-import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
 import static com.android.wm.shell.shared.desktopmode.DesktopModeFlags.WALLPAPER_ACTIVITY;
 
 import android.animation.Animator;
@@ -232,9 +231,7 @@
         LauncherState state = mLauncher.getStateManager().getState();
         boolean nonInteractiveState = state.hasFlag(FLAG_NON_INTERACTIVE)
                 && !state.isTaskbarAlignedWithHotseat(mLauncher);
-        if ((ENABLE_SHELL_TRANSITIONS
-                && isVisible
-                && (nonInteractiveState || mSkipLauncherVisibilityChange))) {
+        if (isVisible && (nonInteractiveState || mSkipLauncherVisibilityChange)) {
             return null;
         }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
index 475b516..ec710c5 100644
--- a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
@@ -41,8 +41,8 @@
 import com.android.launcher3.util.MultiPropertyFactory;
 import com.android.launcher3.util.MultiValueAlpha;
 import com.android.quickstep.NavHandle;
-import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
 import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
+import com.android.wm.shell.shared.handles.RegionSamplingHelper;
 
 import java.io.PrintWriter;
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 0c1235c..47ae741 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -29,7 +29,6 @@
 import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE;
 import static com.android.launcher3.AbstractFloatingView.TYPE_TASKBAR_OVERLAY_PROXY;
 import static com.android.launcher3.Flags.enableCursorHoverStates;
-import static com.android.launcher3.Flags.enableTaskbarCustomization;
 import static com.android.launcher3.Utilities.calculateTextHeight;
 import static com.android.launcher3.Utilities.isRunningInTestHarness;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
@@ -230,15 +229,12 @@
         mNavigationBarPanelContext = navigationBarPanelContext;
         applyDeviceProfile(launcherDp);
         final Resources resources = getResources();
-
-        if (enableTaskbarCustomization()) {
-            mTaskbarFeatureEvaluator = TaskbarFeatureEvaluator.getInstance(this);
-            mTaskbarSpecsEvaluator = new TaskbarSpecsEvaluator(
-                    this,
-                    mTaskbarFeatureEvaluator,
-                    mDeviceProfile.inv.numRows,
-                    mDeviceProfile.inv.numColumns);
-        }
+        mTaskbarFeatureEvaluator = TaskbarFeatureEvaluator.getInstance(this);
+        mTaskbarSpecsEvaluator = new TaskbarSpecsEvaluator(
+                this,
+                mTaskbarFeatureEvaluator,
+                mDeviceProfile.inv.numRows,
+                mDeviceProfile.inv.numColumns);
 
         mImeDrawsImeNavBar = getBoolByName(IME_DRAWS_IME_NAV_BAR_RES_NAME, resources, false);
         mIsSafeModeEnabled = TraceHelper.allowIpcs("isSafeMode",
@@ -287,7 +283,7 @@
             TaskbarHotseatDimensionsProvider dimensionsProvider =
                     new DeviceProfileDimensionsProviderAdapter(this);
             BubbleStashController bubbleStashController = isTransientTaskbar
-                    ? new TransientBubbleStashController(dimensionsProvider, getResources())
+                    ? new TransientBubbleStashController(dimensionsProvider, this)
                     : new PersistentBubbleStashController(dimensionsProvider);
             bubbleControllersOptional = Optional.of(new BubbleControllers(
                     new BubbleBarController(this, bubbleBarView),
@@ -1761,12 +1757,10 @@
         return mControllers.taskbarStashController.isInStashedLauncherState();
     }
 
-    @Nullable
     public TaskbarFeatureEvaluator getTaskbarFeatureEvaluator() {
         return mTaskbarFeatureEvaluator;
     }
 
-    @Nullable
     public TaskbarSpecsEvaluator getTaskbarSpecsEvaluator() {
         return mTaskbarSpecsEvaluator;
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupView.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupView.kt
index a635537..b5a3314 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupView.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupView.kt
@@ -169,8 +169,11 @@
 
     override fun addArrow() {
         super.addArrow()
+        val location = IntArray(2)
+        popupContainer.getLocationInDragLayer(dividerView, location)
+        val dividerViewX = location[0].toFloat()
         // Change arrow location to the middle of popup.
-        mArrow.x = (dividerView.x + dividerView.width / 2) - (mArrowWidth / 2)
+        mArrow.x = (dividerViewX + dividerView.width / 2) - (mArrowWidth / 2)
     }
 
     override fun updateArrowColor() {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
index acf976f..2845cee 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
@@ -171,6 +171,10 @@
     }
 
     private void updateBackgroundAlpha() {
+        if (mActivity.isPhoneMode()) {
+            return;
+        }
+
         final float bgNavbar = mBgNavbar.value;
         final float bgTaskbar = mBgTaskbar.value * mKeyguardBgTaskbar.value
                 * mNotificationShadeBgTaskbar.value * mImeBgTaskbar.value
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index 43960a1..6b1173a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -20,7 +20,6 @@
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
 import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_APP;
-import static com.android.quickstep.OverviewCommandHelper.TYPE_HIDE;
 
 import android.content.Intent;
 import android.graphics.drawable.BitmapDrawable;
@@ -40,6 +39,7 @@
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.SplitConfigurationOptions;
 import com.android.quickstep.OverviewCommandHelper;
+import com.android.quickstep.OverviewCommandHelper.CommandType;
 import com.android.quickstep.util.GroupTask;
 import com.android.quickstep.util.TISBindHelper;
 import com.android.quickstep.views.RecentsView;
@@ -394,7 +394,7 @@
         if (overviewCommandHelper == null) {
             return;
         }
-        overviewCommandHelper.addCommand(TYPE_HIDE);
+        overviewCommandHelper.addCommand(CommandType.HIDE);
     }
 
     /**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index 54a7fdc..8b8b4da 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -22,10 +22,8 @@
 import static com.android.launcher3.Flags.enableRecentsInTaskbar;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_ALL_APPS_SEARCH_IN_TASKBAR;
 import static com.android.launcher3.config.FeatureFlags.enableTaskbarPinning;
 import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
 import android.content.Context;
 import android.content.res.Resources;
@@ -39,12 +37,9 @@
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.ViewConfiguration;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.FrameLayout;
 
-import androidx.annotation.DimenRes;
-import androidx.annotation.DrawableRes;
 import androidx.annotation.LayoutRes;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -62,13 +57,12 @@
 import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.taskbar.customization.TaskbarAllAppsButtonContainer;
+import com.android.launcher3.taskbar.customization.TaskbarDividerContainer;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.LauncherBindableItemsContainer;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.ActivityContext;
-import com.android.launcher3.views.IconButtonView;
-import com.android.quickstep.DeviceConfigWrapper;
-import com.android.quickstep.util.AssistStateManager;
 import com.android.quickstep.util.DesktopTask;
 import com.android.quickstep.util.GroupTask;
 import com.android.systemui.shared.recents.model.Task;
@@ -100,16 +94,13 @@
     private View.OnLongClickListener mIconLongClickListener;
 
     // Only non-null when the corresponding Folder is open.
-    private @Nullable FolderIcon mLeaveBehindFolderIcon;
+    @Nullable private FolderIcon mLeaveBehindFolderIcon;
 
     // Only non-null when device supports having an All Apps button.
-    private @Nullable IconButtonView mAllAppsButton;
-    private Runnable mAllAppsTouchRunnable;
-    private long mAllAppsButtonTouchDelayMs;
-    private boolean mAllAppsTouchTriggered;
+    @Nullable private final TaskbarAllAppsButtonContainer mAllAppsButtonContainer;
 
-    // Only non-null when device supports having an All Apps button, or Recent Apps.
-    private @Nullable IconButtonView mTaskbarDivider;
+    // Only non-null when device supports having an All Apps button.
+    @Nullable private TaskbarDividerContainer mTaskbarDividerContainer;
 
     /**
      * Whether the divider is between Hotseat icons and Recents,
@@ -173,55 +164,14 @@
         // Needed to draw folder leave-behind when opening one.
         setWillNotDraw(false);
 
-        mAllAppsButton = (IconButtonView) LayoutInflater.from(context)
-                .inflate(R.layout.taskbar_all_apps_button, this, false);
-        mAllAppsButton.setIconDrawable(resources.getDrawable(
-                getAllAppsButton(isTransientTaskbar)));
-        mAllAppsButton.setPadding(mItemPadding, mItemPadding, mItemPadding, mItemPadding);
-        mAllAppsButton.setForegroundTint(
-                mActivityContext.getColor(R.color.all_apps_button_color));
+        mAllAppsButtonContainer = new TaskbarAllAppsButtonContainer(context);
 
         if (enableTaskbarPinning() || enableRecentsInTaskbar()) {
-            mTaskbarDivider = (IconButtonView) LayoutInflater.from(context).inflate(
-                    R.layout.taskbar_divider,
-                    this, false);
-            mTaskbarDivider.setIconDrawable(
-                    resources.getDrawable(R.drawable.taskbar_divider_button));
-            mTaskbarDivider.setPadding(mItemPadding, mItemPadding, mItemPadding, mItemPadding);
+            mTaskbarDividerContainer = new TaskbarDividerContainer(context);
         }
 
         // TODO: Disable touch events on QSB otherwise it can crash.
         mQsb = LayoutInflater.from(context).inflate(R.layout.search_container_hotseat, this, false);
-
-        // Default long press (touch) delay = 400ms
-        mAllAppsButtonTouchDelayMs = ViewConfiguration.getLongPressTimeout();
-    }
-
-    @DrawableRes
-    private int getAllAppsButton(boolean isTransientTaskbar) {
-        boolean shouldSelectTransientIcon =
-                (isTransientTaskbar || enableTaskbarPinning())
-                && !mActivityContext.isThreeButtonNav();
-        if (ENABLE_ALL_APPS_SEARCH_IN_TASKBAR.get()) {
-            return shouldSelectTransientIcon
-                    ? R.drawable.ic_transient_taskbar_all_apps_search_button
-                    : R.drawable.ic_taskbar_all_apps_search_button;
-        } else {
-            return shouldSelectTransientIcon
-                    ? R.drawable.ic_transient_taskbar_all_apps_button
-                    : R.drawable.ic_taskbar_all_apps_button;
-        }
-    }
-
-    @DimenRes
-    public int getAllAppsButtonTranslationXOffset(boolean isTransientTaskbar) {
-        if (isTransientTaskbar) {
-            return R.dimen.transient_taskbar_all_apps_button_translation_x_offset;
-        } else {
-            return ENABLE_ALL_APPS_SEARCH_IN_TASKBAR.get()
-                    ? R.dimen.taskbar_all_apps_search_button_translation_x_offset
-                    : R.dimen.taskbar_all_apps_button_translation_x_offset;
-        }
     }
 
     @Override
@@ -306,27 +256,11 @@
         mIconClickListener = mControllerCallbacks.getIconOnClickListener();
         mIconLongClickListener = mControllerCallbacks.getIconOnLongClickListener();
 
-        if (mAllAppsButton != null) {
-            mAllAppsButton.setOnClickListener(this::onAllAppsButtonClick);
-            mAllAppsButton.setOnLongClickListener(this::onAllAppsButtonLongClick);
-            mAllAppsButton.setOnTouchListener(this::onAllAppsButtonTouch);
-            mAllAppsButton.setHapticFeedbackEnabled(
-                    mControllerCallbacks.isAllAppsButtonHapticFeedbackEnabled());
-            mAllAppsTouchRunnable = () -> {
-                mControllerCallbacks.triggerAllAppsButtonLongClick();
-                mAllAppsTouchTriggered = true;
-            };
-            AssistStateManager assistStateManager = AssistStateManager.INSTANCE.get(mContext);
-            if (DeviceConfigWrapper.get().getCustomLpaaThresholds()
-                    && assistStateManager.getLPNHDurationMillis().isPresent()) {
-                mAllAppsButtonTouchDelayMs = assistStateManager.getLPNHDurationMillis().get();
-            }
+        if (mAllAppsButtonContainer != null) {
+            mAllAppsButtonContainer.setUpCallbacks(callbacks);
         }
-        if (mTaskbarDivider != null && !mActivityContext.isThreeButtonNav()) {
-            mTaskbarDivider.setOnLongClickListener(
-                    mControllerCallbacks.getTaskbarDividerLongClickListener());
-            mTaskbarDivider.setOnTouchListener(
-                    mControllerCallbacks.getTaskbarDividerRightClickListener());
+        if (mTaskbarDividerContainer != null && callbacks.supportsDividerLongPress()) {
+            mTaskbarDividerContainer.setUpCallbacks(callbacks);
         }
     }
 
@@ -348,11 +282,11 @@
         int numViewsAnimated = 0;
         mAddedDividerForRecents = false;
 
-        if (mAllAppsButton != null) {
-            removeView(mAllAppsButton);
+        if (mAllAppsButtonContainer != null) {
+            removeView(mAllAppsButtonContainer);
 
-            if (mTaskbarDivider != null) {
-                removeView(mTaskbarDivider);
+            if (mTaskbarDividerContainer != null) {
+                removeView(mTaskbarDividerContainer);
             }
         }
         removeView(mQsb);
@@ -439,8 +373,8 @@
             nextViewIndex++;
         }
 
-        if (mTaskbarDivider != null && !recentTasks.isEmpty()) {
-            addView(mTaskbarDivider, nextViewIndex++);
+        if (mTaskbarDividerContainer != null && !recentTasks.isEmpty()) {
+            addView(mTaskbarDividerContainer, nextViewIndex++);
             mAddedDividerForRecents = true;
         }
 
@@ -504,12 +438,14 @@
             removeAndRecycle(getChildAt(nextViewIndex));
         }
 
-        if (mAllAppsButton != null) {
-            addView(mAllAppsButton, mIsRtl ? hotseatItemInfos.length : 0);
+        if (mAllAppsButtonContainer != null) {
+            addView(mAllAppsButtonContainer, mIsRtl ? hotseatItemInfos.length : 0);
 
             // If there are no recent tasks, add divider after All Apps (unless it's the only view).
-            if (!mAddedDividerForRecents && mTaskbarDivider != null && getChildCount() > 1) {
-                addView(mTaskbarDivider, mIsRtl ? (getChildCount() - 1) : 1);
+            if (!mAddedDividerForRecents
+                    && mTaskbarDividerContainer != null
+                    && getChildCount() > 1) {
+                addView(mTaskbarDividerContainer, mIsRtl ? (getChildCount() - 1) : 1);
             }
         }
         if (mActivityContext.getDeviceProfile().isQsbInline) {
@@ -637,7 +573,7 @@
                 int qsbTop = (bottom - top - deviceProfile.hotseatQsbHeight) / 2;
                 int qsbBottom = qsbTop + deviceProfile.hotseatQsbHeight;
                 child.layout(qsbStart, qsbTop, qsbEnd, qsbBottom);
-            } else if (child == mTaskbarDivider) {
+            } else if (child == mTaskbarDividerContainer) {
                 iconEnd += mItemMarginLeftRight;
                 int iconStart = iconEnd - mIconTouchSize;
                 child.layout(iconStart, mIconLayoutBounds.top, iconEnd, mIconLayoutBounds.bottom);
@@ -727,16 +663,16 @@
      * Returns the all apps button in the taskbar.
      */
     @Nullable
-    public View getAllAppsButtonView() {
-        return mAllAppsButton;
+    public TaskbarAllAppsButtonContainer getAllAppsButtonContainer() {
+        return mAllAppsButtonContainer;
     }
 
     /**
      * Returns the taskbar divider in the taskbar.
      */
     @Nullable
-    public View getTaskbarDividerView() {
-        return mTaskbarDivider;
+    public TaskbarDividerContainer getTaskbarDividerViewContainer() {
+        return mTaskbarDividerContainer;
     }
 
     /**
@@ -832,48 +768,6 @@
                 }
             }
         }
-        return mAllAppsButton;
-    }
-
-    private boolean onAllAppsButtonTouch(View view, MotionEvent ev) {
-        switch (ev.getAction()) {
-            case MotionEvent.ACTION_DOWN:
-                mAllAppsTouchTriggered = false;
-                MAIN_EXECUTOR.getHandler().postDelayed(
-                        mAllAppsTouchRunnable, mAllAppsButtonTouchDelayMs);
-                break;
-            case MotionEvent.ACTION_UP:
-            case MotionEvent.ACTION_CANCEL:
-                cancelAllAppsButtonTouch();
-        }
-        return false;
-    }
-
-    private void cancelAllAppsButtonTouch() {
-        MAIN_EXECUTOR.getHandler().removeCallbacks(mAllAppsTouchRunnable);
-        // ACTION_UP is first triggered, then click listener / long-click listener is triggered on
-        // the next frame, so we need to post twice and delay the reset.
-        if (mAllAppsButton != null) {
-            mAllAppsButton.post(() -> {
-                mAllAppsButton.post(() -> {
-                    mAllAppsTouchTriggered = false;
-                });
-            });
-        }
-    }
-
-    private void onAllAppsButtonClick(View view) {
-        if (!mAllAppsTouchTriggered) {
-            mControllerCallbacks.triggerAllAppsButtonClick(view);
-        }
-    }
-
-    // Handle long click from Switch Access and Voice Access
-    private boolean onAllAppsButtonLongClick(View view) {
-        if (!MAIN_EXECUTOR.getHandler().hasCallbacks(mAllAppsTouchRunnable)
-                && !mAllAppsTouchTriggered) {
-            mControllerCallbacks.triggerAllAppsButtonLongClick();
-        }
-        return true;
+        return mAllAppsButtonContainer;
     }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java
index e6cac2f..296d379 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java
@@ -51,7 +51,7 @@
     }
 
     /** Trigger All Apps button click action. */
-    protected void triggerAllAppsButtonClick(View v) {
+    public void triggerAllAppsButtonClick(View v) {
         InteractionJankMonitorWrapper.begin(v, Cuj.CUJ_LAUNCHER_OPEN_ALL_APPS,
                 /* tag= */ "TASKBAR_BUTTON");
         mActivity.getStatsLogManager().logger().log(LAUNCHER_TASKBAR_ALLAPPS_BUTTON_TAP);
@@ -59,7 +59,7 @@
     }
 
     /** Trigger All Apps button long click action. */
-    protected void triggerAllAppsButtonLongClick() {
+    public void triggerAllAppsButtonLongClick() {
         mActivity.getStatsLogManager().logger().log(LAUNCHER_TASKBAR_ALLAPPS_BUTTON_LONG_PRESS);
     }
 
@@ -74,6 +74,11 @@
         };
     }
 
+    /** Check to see if we support long press on taskbar divider */
+    public boolean supportsDividerLongPress() {
+        return !mActivity.isThreeButtonNav();
+    }
+
     public View.OnTouchListener getTaskbarDividerRightClickListener() {
         return (v, event) -> {
             if (event.isFromSource(InputDevice.SOURCE_MOUSE)
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 27c1d9c..aa3e6bf 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -72,7 +72,6 @@
 import com.android.launcher3.util.MultiPropertyFactory;
 import com.android.launcher3.util.MultiTranslateDelegate;
 import com.android.launcher3.util.MultiValueAlpha;
-import com.android.launcher3.views.IconButtonView;
 import com.android.quickstep.util.GroupTask;
 import com.android.systemui.shared.recents.model.Task;
 
@@ -299,7 +298,7 @@
 
     @Nullable
     public View getAllAppsButtonView() {
-        return mTaskbarView.getAllAppsButtonView();
+        return mTaskbarView.getAllAppsButtonContainer();
     }
 
     public AnimatedFloat getTaskbarIconScaleForStash() {
@@ -366,9 +365,9 @@
         View[] iconViews = mTaskbarView.getIconViews();
         float scale = mTaskbarIconTranslationXForPinning.value;
         float transientTaskbarAllAppsOffset = mActivity.getResources().getDimension(
-                mTaskbarView.getAllAppsButtonTranslationXOffset(true));
+                mTaskbarView.getAllAppsButtonContainer().getAllAppsButtonTranslationXOffset(true));
         float persistentTaskbarAllAppsOffset = mActivity.getResources().getDimension(
-                mTaskbarView.getAllAppsButtonTranslationXOffset(false));
+                mTaskbarView.getAllAppsButtonContainer().getAllAppsButtonTranslationXOffset(false));
 
         float allAppIconTranslateRange = mapRange(scale, transientTaskbarAllAppsOffset,
                 persistentTaskbarAllAppsOffset);
@@ -383,7 +382,7 @@
         }
 
         if (mActivity.isThreeButtonNav()) {
-            ((IconButtonView) mTaskbarView.getAllAppsButtonView())
+            mTaskbarView.getAllAppsButtonContainer()
                     .setTranslationXForTaskbarAllAppsIcon(allAppIconTranslateRange);
             return;
         }
@@ -408,8 +407,8 @@
                         -finalMarginScale * (iconIndex - halfIconCount));
             }
 
-            if (iconView.equals(mTaskbarView.getAllAppsButtonView())) {
-                ((IconButtonView) iconView).setTranslationXForTaskbarAllAppsIcon(
+            if (iconView.equals(mTaskbarView.getAllAppsButtonContainer())) {
+                mTaskbarView.getAllAppsButtonContainer().setTranslationXForTaskbarAllAppsIcon(
                         allAppIconTranslateRange);
             }
         }
@@ -537,7 +536,7 @@
     }
 
     public View getTaskbarDividerView() {
-        return mTaskbarView.getTaskbarDividerView();
+        return mTaskbarView.getTaskbarDividerViewContainer();
     }
 
     /**
@@ -753,8 +752,8 @@
         int firstRecentTaskIndex = -1;
         for (int i = 0; i < mTaskbarView.getChildCount(); i++) {
             View child = mTaskbarView.getChildAt(i);
-            boolean isAllAppsButton = child == mTaskbarView.getAllAppsButtonView();
-            boolean isTaskbarDividerView = child == mTaskbarView.getTaskbarDividerView();
+            boolean isAllAppsButton = child == mTaskbarView.getAllAppsButtonContainer();
+            boolean isTaskbarDividerView = child == mTaskbarView.getTaskbarDividerViewContainer();
             boolean isRecentTask = child.getTag() instanceof GroupTask;
             // TODO(b/343522351): show recents on the home screen.
             final boolean isRecentsInHotseat = false;
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
index 90ac872..f5ac66f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
@@ -16,7 +16,6 @@
 package com.android.launcher3.taskbar.allapps;
 
 import static com.android.app.animation.Interpolators.EMPHASIZED;
-import static com.android.launcher3.Flags.enablePredictiveBackGesture;
 import static com.android.launcher3.touch.AllAppsSwipeController.ALL_APPS_FADE_MANUAL;
 import static com.android.launcher3.touch.AllAppsSwipeController.SCRIM_FADE_MANUAL;
 
@@ -193,14 +192,12 @@
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
         mActivityContext.addOnDeviceProfileChangeListener(this);
-        if (enablePredictiveBackGesture()) {
-            mAppsView.getAppsRecyclerViewContainer().setOutlineProvider(mViewOutlineProvider);
-            mAppsView.getAppsRecyclerViewContainer().setClipToOutline(true);
-            OnBackInvokedDispatcher dispatcher = findOnBackInvokedDispatcher();
-            if (dispatcher != null) {
-                dispatcher.registerOnBackInvokedCallback(
-                        OnBackInvokedDispatcher.PRIORITY_DEFAULT, this);
-            }
+        mAppsView.getAppsRecyclerViewContainer().setOutlineProvider(mViewOutlineProvider);
+        mAppsView.getAppsRecyclerViewContainer().setClipToOutline(true);
+        OnBackInvokedDispatcher dispatcher = findOnBackInvokedDispatcher();
+        if (dispatcher != null) {
+            dispatcher.registerOnBackInvokedCallback(
+                    OnBackInvokedDispatcher.PRIORITY_DEFAULT, this);
         }
     }
 
@@ -208,13 +205,11 @@
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
         mActivityContext.removeOnDeviceProfileChangeListener(this);
-        if (enablePredictiveBackGesture()) {
-            mAppsView.getAppsRecyclerViewContainer().setOutlineProvider(null);
-            mAppsView.getAppsRecyclerViewContainer().setClipToOutline(false);
-            OnBackInvokedDispatcher dispatcher = findOnBackInvokedDispatcher();
-            if (dispatcher != null) {
-                dispatcher.unregisterOnBackInvokedCallback(this);
-            }
+        mAppsView.getAppsRecyclerViewContainer().setOutlineProvider(null);
+        mAppsView.getAppsRecyclerViewContainer().setClipToOutline(false);
+        OnBackInvokedDispatcher dispatcher = findOnBackInvokedDispatcher();
+        if (dispatcher != null) {
+            dispatcher.unregisterOnBackInvokedCallback(this);
         }
     }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
index c458936..71867fe 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
@@ -1473,8 +1473,9 @@
         pw.println("BubbleBarView state:");
         pw.println("  visibility: " + getVisibility());
         pw.println("  alpha: " + getAlpha());
-        pw.println("  translation Y: " + getTranslationY());
-        pw.println("  bubbles in bar (childCount = " + getChildCount() + ")");
+        pw.println("  translationY: " + getTranslationY());
+        pw.println("  childCount: " + getChildCount());
+        pw.println("  hasOverflow:  " + hasOverflow());
         for (BubbleView bubbleView: getBubbles()) {
             BubbleBarItem bubble = bubbleView.getBubble();
             String key = bubble == null ? "null" : bubble.getKey();
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
index 5c1a546..2502e4a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
@@ -80,12 +80,14 @@
 
     // These are exposed to {@link BubbleStashController} to animate for stashing/un-stashing
     private final MultiValueAlpha mBubbleBarAlpha;
-    private final AnimatedFloat mBubbleBarScale = new AnimatedFloat(this::updateScale);
+    private final AnimatedFloat mBubbleBarScaleY = new AnimatedFloat(this::updateScaleY);
     private final AnimatedFloat mBubbleBarTranslationY = new AnimatedFloat(
             this::updateTranslationY);
 
     // Modified when swipe up is happening on the bubble bar or task bar.
     private float mBubbleBarSwipeUpTranslationY;
+    // Modified when bubble bar is springing back into the stash handle.
+    private float mBubbleBarStashTranslationY;
 
     // Whether the bar is hidden for a sysui state.
     private boolean mHiddenForSysui;
@@ -125,7 +127,7 @@
         onBubbleBarConfigurationChanged(/* animate= */ false);
         mActivity.addOnDeviceProfileChangeListener(
                 dp -> onBubbleBarConfigurationChanged(/* animate= */ true));
-        mBubbleBarScale.updateValue(1f);
+        mBubbleBarScaleY.updateValue(1f);
         mBubbleClickListener = v -> onBubbleClicked((BubbleView) v);
         mBubbleBarClickListener = v -> expandBubbleBar();
         mBubbleDragController.setupBubbleBarView(mBarView);
@@ -255,8 +257,8 @@
         return mBubbleBarAlpha;
     }
 
-    public AnimatedFloat getBubbleBarScale() {
-        return mBubbleBarScale;
+    public AnimatedFloat getBubbleBarScaleY() {
+        return mBubbleBarScaleY;
     }
 
     public AnimatedFloat getBubbleBarTranslationY() {
@@ -268,6 +270,27 @@
     }
 
     /**
+     * @see BubbleBarView#getRelativePivotX()
+     */
+    public float getBubbleBarRelativePivotX() {
+        return mBarView.getRelativePivotX();
+    }
+
+    /**
+     * @see BubbleBarView#getRelativePivotY()
+     */
+    public float getBubbleBarRelativePivotY() {
+        return mBarView.getRelativePivotY();
+    }
+
+    /**
+     * @see BubbleBarView#setRelativePivot(float, float)
+     */
+    public void setBubbleBarRelativePivot(float x, float y) {
+        mBarView.setRelativePivot(x, y);
+    }
+
+    /**
      * Whether the bubble bar is visible or not.
      */
     public boolean isBubbleBarVisible() {
@@ -474,17 +497,20 @@
         updateTranslationY();
     }
 
-    private void updateTranslationY() {
-        mBarView.setTranslationY(mBubbleBarTranslationY.value
-                + mBubbleBarSwipeUpTranslationY);
+    /**
+     * Sets the translation of the bubble bar during the stash animation.
+     */
+    public void setTranslationYForStash(float transY) {
+        mBubbleBarStashTranslationY = transY;
+        updateTranslationY();
     }
 
-    /**
-     * Applies scale properties for the entire bubble bar.
-     */
-    private void updateScale() {
-        float scale = mBubbleBarScale.value;
-        mBarView.setScaleX(scale);
+    private void updateTranslationY() {
+        mBarView.setTranslationY(mBubbleBarTranslationY.value + mBubbleBarSwipeUpTranslationY
+                + mBubbleBarStashTranslationY);
+    }
+
+    private void updateScaleY(float scale) {
         mBarView.setScaleY(scale);
     }
 
@@ -793,6 +819,7 @@
         pw.println("  mShouldShowEducation: " + mShouldShowEducation);
         pw.println("  mBubbleBarTranslationY.value: " + mBubbleBarTranslationY.value);
         pw.println("  mBubbleBarSwipeUpTranslationY: " + mBubbleBarSwipeUpTranslationY);
+        pw.println("  mOverflowAdded: " + mOverflowAdded);
         if (mBarView != null) {
             mBarView.dump(pw);
         } else {
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java
index 6bfe8f4..16af248 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java
@@ -21,6 +21,7 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
+import android.annotation.Nullable;
 import android.content.res.Resources;
 import android.graphics.Outline;
 import android.graphics.Rect;
@@ -28,6 +29,7 @@
 import android.view.View;
 import android.view.ViewOutlineProvider;
 
+import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.RevealOutlineAnimation;
 import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
@@ -37,9 +39,9 @@
 import com.android.launcher3.util.Executors;
 import com.android.launcher3.util.MultiPropertyFactory;
 import com.android.launcher3.util.MultiValueAlpha;
-import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
 import com.android.wm.shell.common.bubbles.BubbleBarLocation;
 import com.android.wm.shell.shared.animation.PhysicsAnimator;
+import com.android.wm.shell.shared.handles.RegionSamplingHelper;
 
 /**
  * Handles properties/data collection, then passes the results to our stashed handle View to render.
@@ -49,23 +51,29 @@
     private final TaskbarActivityContext mActivity;
     private final StashedHandleView mStashedHandleView;
     private final MultiValueAlpha mStashedHandleAlpha;
+    private float mTranslationForSwipeY;
+    private float mTranslationForStashY;
 
     // Initialized in init.
     private BubbleBarViewController mBarViewController;
     private BubbleStashController mBubbleStashController;
     private RegionSamplingHelper mRegionSamplingHelper;
-    private int mBarSize;
-    private int mStashedTaskbarHeight;
+    // Height of the area for the stash handle. Handle will be drawn in the center of this.
+    // This is also the area where touch is handled on the handle.
+    private int mStashedBubbleBarHeight;
     private int mStashedHandleWidth;
     private int mStashedHandleHeight;
 
-    // The bounds we want to clip to in the settled state when showing the stashed handle.
+    // The bounds of the stashed handle in settled state.
     private final Rect mStashedHandleBounds = new Rect();
+    private float mStashedHandleRadius;
 
     // When the reveal animation is cancelled, we can assume it's about to create a new animation,
     // which should start off at the same point the cancelled one left off.
     private float mStartProgressForNextRevealAnim;
-    private boolean mWasLastRevealAnimReversed;
+    // Use a nullable boolean to handle initial case where the last animation direction is not known
+    @Nullable
+    private Boolean mWasLastRevealAnimReversed = null;
 
     // XXX: if there are more of these maybe do state flags instead
     private boolean mHiddenForSysui;
@@ -77,6 +85,7 @@
         mActivity = activity;
         mStashedHandleView = stashedHandleView;
         mStashedHandleAlpha = new MultiValueAlpha(mStashedHandleView, 1);
+        mStashedHandleAlpha.setUpdateVisibility(true);
     }
 
     /** Initialize controller. */
@@ -84,26 +93,31 @@
         mBarViewController = bubbleControllers.bubbleBarViewController;
         mBubbleStashController = bubbleControllers.bubbleStashController;
 
+        DeviceProfile deviceProfile = mActivity.getDeviceProfile();
         Resources resources = mActivity.getResources();
         mStashedHandleHeight = resources.getDimensionPixelSize(
                 R.dimen.bubblebar_stashed_handle_height);
         mStashedHandleWidth = resources.getDimensionPixelSize(
                 R.dimen.bubblebar_stashed_handle_width);
-        mBarSize = resources.getDimensionPixelSize(R.dimen.bubblebar_size);
 
-        final int bottomMargin = resources.getDimensionPixelSize(
-                R.dimen.transient_taskbar_bottom_margin);
-        mStashedHandleView.getLayoutParams().height = mBarSize + bottomMargin;
+        int barSize = resources.getDimensionPixelSize(R.dimen.bubblebar_size);
+        // Use the max translation for bubble bar whether it is on the home screen or in app.
+        // Use values directly from device profile to avoid referencing other bubble controllers
+        // during init flow.
+        int maxTy = Math.max(deviceProfile.hotseatBarBottomSpacePx,
+                deviceProfile.taskbarBottomMargin);
+        // Adjust handle view size to accommodate the handle morphing into the bubble bar
+        mStashedHandleView.getLayoutParams().height = barSize + maxTy;
 
         mStashedHandleAlpha.get(0).setValue(0);
 
-        mStashedTaskbarHeight = resources.getDimensionPixelSize(
+        mStashedBubbleBarHeight = resources.getDimensionPixelSize(
                 R.dimen.bubblebar_stashed_size);
         mStashedHandleView.setOutlineProvider(new ViewOutlineProvider() {
             @Override
             public void getOutline(View view, Outline outline) {
-                float stashedHandleRadius = view.getHeight() / 2f;
-                outline.setRoundRect(mStashedHandleBounds, stashedHandleRadius);
+                mStashedHandleRadius = view.getHeight() / 2f;
+                outline.setRoundRect(mStashedHandleBounds, mStashedHandleRadius);
             }
         });
 
@@ -132,28 +146,25 @@
     private void updateBounds(BubbleBarLocation bubbleBarLocation) {
         // As more bubbles get added, the icon bounds become larger. To ensure a consistent
         // handle bar position, we pin it to the edge of the screen.
-        final int stashedCenterY = mStashedHandleView.getHeight() - mStashedTaskbarHeight / 2;
+        final int stashedCenterY = mStashedHandleView.getHeight() - mStashedBubbleBarHeight / 2;
+        final int stashedCenterX;
         if (bubbleBarLocation.isOnLeft(mStashedHandleView.isLayoutRtl())) {
             final int left = mBarViewController.getHorizontalMargin();
-            mStashedHandleBounds.set(
-                    left,
-                    stashedCenterY - mStashedHandleHeight / 2,
-                    left + mStashedHandleWidth,
-                    stashedCenterY + mStashedHandleHeight / 2);
-            mStashedHandleView.setPivotX(0);
+            stashedCenterX = left + mStashedHandleWidth / 2;
         } else {
             final int right =
-                    mActivity.getDeviceProfile().widthPx - mBarViewController.getHorizontalMargin();
-            mStashedHandleBounds.set(
-                    right - mStashedHandleWidth,
-                    stashedCenterY - mStashedHandleHeight / 2,
-                    right,
-                    stashedCenterY + mStashedHandleHeight / 2);
-            mStashedHandleView.setPivotX(mStashedHandleView.getWidth());
+                    mStashedHandleView.getRight() - mBarViewController.getHorizontalMargin();
+            stashedCenterX = right - mStashedHandleWidth / 2;
         }
-
+        mStashedHandleBounds.set(
+                stashedCenterX - mStashedHandleWidth / 2,
+                stashedCenterY - mStashedHandleHeight / 2,
+                stashedCenterX + mStashedHandleWidth / 2,
+                stashedCenterY + mStashedHandleHeight / 2
+        );
         mStashedHandleView.updateSampledRegion(mStashedHandleBounds);
-        mStashedHandleView.setPivotY(mStashedHandleView.getHeight() - mStashedTaskbarHeight / 2f);
+        mStashedHandleView.setPivotX(stashedCenterX);
+        mStashedHandleView.setPivotY(stashedCenterY);
     }
 
     public void onDestroy() {
@@ -169,13 +180,6 @@
     }
 
     /**
-     * Returns the height when the bubble bar is unstashed (so the height of the bubble bar).
-     */
-    public int getUnstashedHeight() {
-        return mBarSize;
-    }
-
-    /**
      * Called when system ui state changes. Bubbles don't show when the device is locked.
      */
     public void setHiddenForSysui(boolean hidden) {
@@ -242,7 +246,20 @@
      * Sets the translation of the stashed handle during the swipe up gesture.
      */
     public void setTranslationYForSwipe(float transY) {
-        mStashedHandleView.setTranslationY(transY);
+        mTranslationForSwipeY = transY;
+        updateTranslationY();
+    }
+
+    /**
+     * Sets the translation of the stashed handle during the spring on stash animation.
+     */
+    public void setTranslationYForStash(float transY) {
+        mTranslationForStashY = transY;
+        updateTranslationY();
+    }
+
+    private void updateTranslationY() {
+        mStashedHandleView.setTranslationY(mTranslationForSwipeY + mTranslationForStashY);
     }
 
     /** Returns the translation of the stashed handle. */
@@ -263,18 +280,17 @@
      * the size of where the bubble bar icons will be.
      */
     public Animator createRevealAnimToIsStashed(boolean isStashed) {
-        Rect bubbleBarBounds = new Rect(mBarViewController.getBubbleBarBounds());
+        Rect bubbleBarBounds = getLocalBubbleBarBounds();
 
-        // Account for the full visual height of the bubble bar
-        int heightDiff = (mBarSize - bubbleBarBounds.height()) / 2;
-        bubbleBarBounds.top -= heightDiff;
-        bubbleBarBounds.bottom += heightDiff;
-        float stashedHandleRadius = mStashedHandleView.getHeight() / 2f;
+        float bubbleBarRadius = bubbleBarBounds.height() / 2f;
         final RevealOutlineAnimation handleRevealProvider = new RoundedRectRevealOutlineProvider(
-                stashedHandleRadius, stashedHandleRadius, bubbleBarBounds, mStashedHandleBounds);
+                bubbleBarRadius, mStashedHandleRadius, bubbleBarBounds, mStashedHandleBounds);
 
         boolean isReversed = !isStashed;
-        boolean changingDirection = mWasLastRevealAnimReversed != isReversed;
+        // We are only changing direction when mWasLastRevealAnimReversed is set at least once
+        boolean changingDirection =
+                mWasLastRevealAnimReversed != null && mWasLastRevealAnimReversed != isReversed;
+
         mWasLastRevealAnimReversed = isReversed;
         if (changingDirection) {
             mStartProgressForNextRevealAnim = 1f - mStartProgressForNextRevealAnim;
@@ -291,6 +307,21 @@
         return revealAnim;
     }
 
+    /**
+     * Get bounds for the bubble bar in the space of the handle view
+     */
+    private Rect getLocalBubbleBarBounds() {
+        // Position the bubble bar bounds to the space of handle view
+        Rect bubbleBarBounds = new Rect(mBarViewController.getBubbleBarBounds());
+        // Start by moving bubble bar bounds to the bottom of handle view
+        int height = bubbleBarBounds.height();
+        bubbleBarBounds.bottom = mStashedHandleView.getHeight();
+        bubbleBarBounds.top = bubbleBarBounds.bottom - height;
+        // Then apply translation that is applied to the bubble bar
+        bubbleBarBounds.offset(0, (int) mBubbleStashController.getBubbleBarTranslationY());
+        return bubbleBarBounds;
+    }
+
     /** Checks that the stash handle is visible and that the motion event is within bounds. */
     public boolean isEventOverHandle(MotionEvent ev) {
         if (mStashedHandleView.getVisibility() != VISIBLE) {
@@ -299,7 +330,7 @@
 
         // the bounds of the handle only include the visible part, so we check that the Y coordinate
         // is anywhere within the stashed height of bubble bar (same as taskbar stashed height).
-        final int top = mActivity.getDeviceProfile().heightPx - mStashedTaskbarHeight;
+        final int top = mActivity.getDeviceProfile().heightPx - mStashedBubbleBarHeight;
         final float x = ev.getRawX();
         return ev.getRawY() >= top && x >= mStashedHandleBounds.left
                 && x <= mStashedHandleBounds.right;
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
index eb3b24b..6265ec3 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
@@ -237,7 +237,11 @@
         mBubble = bubble;
         mIcon = bubble.getIcon();
         updateBubbleIcon();
-        mAppIcon.setImageBitmap(bubble.getBadge());
+        if (bubble.getInfo().showAppBadge()) {
+            mAppIcon.setImageBitmap(bubble.getBadge());
+        } else {
+            mAppIcon.setVisibility(GONE);
+        }
         mDotColor = bubble.getDotColor();
         mDotRenderer = new DotRenderer(mBubbleSize, bubble.getDotPath(), DEFAULT_PATH_SIZE);
         String contentDesc = bubble.getInfo().getTitle();
@@ -302,8 +306,10 @@
     }
 
     void setBadgeScale(float fraction) {
-        mAppIcon.setScaleX(fraction);
-        mAppIcon.setScaleY(fraction);
+        if (mAppIcon.getVisibility() == VISIBLE) {
+            mAppIcon.setScaleX(fraction);
+            mAppIcon.setScaleY(fraction);
+        }
     }
 
     boolean hasUnseenContent() {
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 48eb7de..b2a88ac 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/BubbleStashController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/BubbleStashController.kt
@@ -179,10 +179,10 @@
         /** How long to stash/unstash. */
         const val BAR_STASH_DURATION = InsetsController.ANIMATION_DURATION_RESIZE.toLong()
 
+        const val BAR_STASH_ALPHA_DURATION = 50L
+        const val BAR_STASH_ALPHA_DELAY = 33L
+
         /** How long to translate Y coordinate of the BubbleBar. */
         const val BAR_TRANSLATION_DURATION = 300L
-
-        /** The scale bubble bar animates to when being stashed. */
-        const val STASHED_BAR_SCALE = 0.5f
     }
 }
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 1b65019..3ebd97e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentBubbleStashController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentBubbleStashController.kt
@@ -116,7 +116,7 @@
         bubbleBarTranslationYAnimator = bubbleBarViewController.bubbleBarTranslationY
         // bubble bar has only alpha property, getting it at index 0
         bubbleBarAlphaAnimator = bubbleBarViewController.bubbleBarAlpha.get(/* index= */ 0)
-        bubbleBarScaleAnimator = bubbleBarViewController.bubbleBarScale
+        bubbleBarScaleAnimator = bubbleBarViewController.bubbleBarScaleY
     }
 
     private fun animateAfterUnlock() {
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 1a4b982..b8fd1cb 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt
@@ -17,30 +17,36 @@
 package com.android.launcher3.taskbar.bubbles.stashing
 
 import android.animation.Animator
-import android.animation.AnimatorListenerAdapter
 import android.animation.AnimatorSet
-import android.content.res.Resources
+import android.content.Context
 import android.view.MotionEvent
 import android.view.View
 import androidx.annotation.VisibleForTesting
+import androidx.core.animation.doOnEnd
+import androidx.core.animation.doOnStart
+import androidx.dynamicanimation.animation.SpringForce
+import com.android.app.animation.Interpolators.EMPHASIZED
+import com.android.app.animation.Interpolators.LINEAR
 import com.android.launcher3.R
 import com.android.launcher3.anim.AnimatedFloat
-import com.android.launcher3.taskbar.StashedHandleViewController
+import com.android.launcher3.anim.SpringAnimationBuilder
 import com.android.launcher3.taskbar.TaskbarInsetsController
 import com.android.launcher3.taskbar.bubbles.BubbleBarViewController
 import com.android.launcher3.taskbar.bubbles.BubbleStashedHandleViewController
+import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.Companion.BAR_STASH_ALPHA_DELAY
+import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.Companion.BAR_STASH_ALPHA_DURATION
 import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.Companion.BAR_STASH_DURATION
 import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.Companion.BAR_TRANSLATION_DURATION
-import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.Companion.STASHED_BAR_SCALE
 import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.ControllersAfterInitAction
 import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.TaskbarHotseatDimensionsProvider
 import com.android.launcher3.util.MultiPropertyFactory
 import com.android.wm.shell.common.bubbles.BubbleBarLocation
 import com.android.wm.shell.shared.animation.PhysicsAnimator
+import kotlin.math.max
 
 class TransientBubbleStashController(
     private val taskbarHotseatDimensionsProvider: TaskbarHotseatDimensionsProvider,
-    resources: Resources
+    private val context: Context
 ) : BubbleStashController {
 
     private lateinit var bubbleBarViewController: BubbleBarViewController
@@ -50,14 +56,20 @@
     // stash view properties
     private var bubbleStashedHandleViewController: BubbleStashedHandleViewController? = null
     private var stashHandleViewAlpha: MultiPropertyFactory<View>.MultiProperty? = null
+    private var translationYDuringStash = AnimatedFloat { transY ->
+        bubbleStashedHandleViewController?.setTranslationYForStash(transY)
+        bubbleBarViewController.setTranslationYForStash(transY)
+    }
+    private val stashHandleStashVelocity =
+        context.resources.getDimension(R.dimen.bubblebar_stashed_handle_spring_velocity_dp_per_s)
     private var stashedHeight: Int = 0
 
     // bubble bar properties
     private lateinit var bubbleBarAlpha: MultiPropertyFactory<View>.MultiProperty
     private lateinit var bubbleBarTranslationYAnimator: AnimatedFloat
     private lateinit var bubbleBarScale: AnimatedFloat
-    private val mHandleCenterFromScreenBottom =
-        resources.getDimensionPixelSize(R.dimen.bubblebar_stashed_size) / 2f
+    private val handleCenterFromScreenBottom =
+        context.resources.getDimensionPixelSize(R.dimen.bubblebar_stashed_size) / 2f
 
     private var animator: AnimatorSet? = null
 
@@ -136,12 +148,9 @@
         bubbleBarTranslationYAnimator = bubbleBarViewController.bubbleBarTranslationY
         // bubble bar has only alpha property, getting it at index 0
         bubbleBarAlpha = bubbleBarViewController.bubbleBarAlpha.get(/* index= */ 0)
-        bubbleBarScale = bubbleBarViewController.bubbleBarScale
+        bubbleBarScale = bubbleBarViewController.bubbleBarScaleY
         stashedHeight = bubbleStashedHandleViewController?.stashedHeight ?: 0
-        stashHandleViewAlpha =
-            bubbleStashedHandleViewController
-                ?.stashedHandleAlpha
-                ?.get(StashedHandleViewController.ALPHA_INDEX_STASHED)
+        stashHandleViewAlpha = bubbleStashedHandleViewController?.stashedHandleAlpha?.get(0)
     }
 
     private fun animateAfterUnlock() {
@@ -179,7 +188,7 @@
         stashHandleViewAlpha?.value = 1f
         this.bubbleBarTranslationYAnimator.updateValue(getStashTranslation())
         bubbleBarAlpha.setValue(0f)
-        bubbleBarScale.updateValue(STASHED_BAR_SCALE)
+        bubbleBarScale.updateValue(getStashScale())
         isStashed = true
         onIsStashedChanged()
     }
@@ -223,11 +232,11 @@
         // the difference between the centers of the handle and the bubble bar is the difference
         // between their distance from the bottom of the screen.
         val barCenter: Float = bubbleBarViewController.bubbleBarCollapsedHeight / 2f
-        return mHandleCenterFromScreenBottom - barCenter
+        return handleCenterFromScreenBottom - barCenter
     }
 
     override fun getStashedHandleTranslationForNewBubbleAnimation(): Float {
-        return -mHandleCenterFromScreenBottom
+        return -handleCenterFromScreenBottom
     }
 
     override fun getStashedHandlePhysicsAnimator(): PhysicsAnimator<View>? {
@@ -245,7 +254,13 @@
     override fun getHandleTranslationY(): Float? = bubbleStashedHandleViewController?.translationY
 
     private fun getStashTranslation(): Float {
-        return (bubbleBarViewController.bubbleBarCollapsedHeight - stashedHeight) / 2f
+        return bubbleBarTranslationY / 2f
+    }
+
+    @VisibleForTesting
+    fun getStashScale(): Float {
+        val handleHeight = bubbleStashedHandleViewController?.stashedHeight ?: 0
+        return handleHeight / bubbleBarViewController.bubbleBarCollapsedHeight
     }
 
     /**
@@ -258,61 +273,84 @@
     @Suppress("SameParameterValue")
     private fun createStashAnimator(isStashed: Boolean, duration: Long): AnimatorSet {
         val animatorSet = AnimatorSet()
-        val fullLengthAnimatorSet = AnimatorSet()
-        // Not exactly half and may overlap. See [first|second]HalfDurationScale below.
-        val firstHalfAnimatorSet = AnimatorSet()
-        val secondHalfAnimatorSet = AnimatorSet()
-        val firstHalfDurationScale: Float
-        val secondHalfDurationScale: Float
-        val stashHandleAlphaValue: Float
-        if (isStashed) {
-            firstHalfDurationScale = 0.75f
-            secondHalfDurationScale = 0.5f
-            stashHandleAlphaValue = 1f
-            fullLengthAnimatorSet.play(
-                bubbleBarTranslationYAnimator.animateToValue(getStashTranslation())
-            )
-            firstHalfAnimatorSet.playTogether(
-                bubbleBarAlpha.animateToValue(0f),
-                bubbleBarScale.animateToValue(STASHED_BAR_SCALE)
-            )
-        } else {
-            firstHalfDurationScale = 0.5f
-            secondHalfDurationScale = 0.75f
-            stashHandleAlphaValue = 0f
-            fullLengthAnimatorSet.playTogether(
-                bubbleBarScale.animateToValue(1f),
-                bubbleBarTranslationYAnimator.animateToValue(bubbleBarTranslationY)
-            )
-            secondHalfAnimatorSet.playTogether(bubbleBarAlpha.animateToValue(1f))
-        }
-        stashHandleViewAlpha?.let {
-            secondHalfAnimatorSet.playTogether(it.animateToValue(stashHandleAlphaValue))
-        }
-        bubbleStashedHandleViewController?.createRevealAnimToIsStashed(isStashed)?.let {
-            fullLengthAnimatorSet.play(it)
-        }
-        fullLengthAnimatorSet.setDuration(duration)
-        firstHalfAnimatorSet.setDuration((duration * firstHalfDurationScale).toLong())
-        secondHalfAnimatorSet.setDuration((duration * secondHalfDurationScale).toLong())
-        secondHalfAnimatorSet.startDelay = (duration * (1 - secondHalfDurationScale)).toLong()
-        animatorSet.playTogether(fullLengthAnimatorSet, firstHalfAnimatorSet, secondHalfAnimatorSet)
-        animatorSet.addListener(
-            object : AnimatorListenerAdapter() {
-                override fun onAnimationEnd(animation: Animator) {
-                    animator = null
-                    controllersAfterInitAction.runAfterInit {
-                        if (isStashed) {
-                            bubbleBarViewController.isExpanded = false
-                        }
-                        taskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged()
-                    }
-                }
+
+        val alphaDuration = if (isStashed) duration else BAR_STASH_ALPHA_DURATION
+        val alphaDelay = if (isStashed) BAR_STASH_ALPHA_DELAY else 0L
+        animatorSet.play(
+            createStashAlphaAnimator(isStashed).apply {
+                this.duration = max(0L, alphaDuration - alphaDelay)
+                this.startDelay = alphaDelay
+                this.interpolator = LINEAR
             }
         )
+
+        animatorSet.play(
+            createSpringOnStashAnimator(isStashed).apply {
+                this.duration = duration
+                this.interpolator = LINEAR
+            }
+        )
+
+        animatorSet.play(
+            bubbleStashedHandleViewController?.createRevealAnimToIsStashed(isStashed)?.apply {
+                this.duration = duration
+                this.interpolator = EMPHASIZED
+            }
+        )
+
+        val scaleTarget = if (isStashed) getStashScale() else 1f
+        animatorSet.play(
+            bubbleBarScale.animateToValue(scaleTarget).apply {
+                this.duration = duration
+                this.interpolator = EMPHASIZED
+                this.setBubbleBarPivotDuringAnim(0.5f, 1f)
+            }
+        )
+
+        val translationYTarget = if (isStashed) getStashTranslation() else bubbleBarTranslationY
+        animatorSet.play(
+            bubbleBarTranslationYAnimator.animateToValue(translationYTarget).apply {
+                this.duration = duration
+                this.interpolator = EMPHASIZED
+            }
+        )
+
+        animatorSet.doOnEnd {
+            animator = null
+            controllersAfterInitAction.runAfterInit {
+                if (isStashed) {
+                    bubbleBarViewController.isExpanded = false
+                }
+                taskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged()
+            }
+        }
         return animatorSet
     }
 
+    private fun createStashAlphaAnimator(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(stashHandleViewAlpha?.animateToValue(stashHandleAlphaTarget))
+        }
+    }
+
+    private fun createSpringOnStashAnimator(isStashed: Boolean): Animator {
+        if (!isStashed) {
+            // Animate the stash translation back to 0
+            return translationYDuringStash.animateToValue(0f)
+        }
+        // Apply a spring to the handle
+        return SpringAnimationBuilder(context)
+            .setStartValue(translationYDuringStash.value)
+            .setEndValue(0f)
+            .setDampingRatio(SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY)
+            .setStiffness(SpringForce.STIFFNESS_LOW)
+            .setStartVelocity(stashHandleStashVelocity)
+            .build(translationYDuringStash, AnimatedFloat.VALUE)
+    }
+
     private fun onIsStashedChanged() {
         controllersAfterInitAction.runAfterInit {
             taskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged()
@@ -363,13 +401,23 @@
     }
 
     private fun Animator.updateTouchRegionOnAnimationEnd(): Animator {
-        this.addListener(
-            object : AnimatorListenerAdapter() {
-                override fun onAnimationEnd(animation: Animator) {
-                    onIsStashedChanged()
-                }
+        doOnEnd { onIsStashedChanged() }
+        return this
+    }
+
+    private fun Animator.setBubbleBarPivotDuringAnim(pivotX: Float, pivotY: Float): Animator {
+        var initialPivotX = Float.NaN
+        var initialPivotY = Float.NaN
+        doOnStart {
+            initialPivotX = bubbleBarViewController.bubbleBarRelativePivotX
+            initialPivotY = bubbleBarViewController.bubbleBarRelativePivotY
+            bubbleBarViewController.setBubbleBarRelativePivot(pivotX, pivotY)
+        }
+        doOnEnd {
+            if (!initialPivotX.isNaN() && !initialPivotY.isNaN()) {
+                bubbleBarViewController.setBubbleBarRelativePivot(initialPivotX, initialPivotY)
             }
-        )
+        }
         return this
     }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarAllAppsButtonContainer.kt b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarAllAppsButtonContainer.kt
index 7d2d36d..726800c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarAllAppsButtonContainer.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarAllAppsButtonContainer.kt
@@ -18,18 +18,27 @@
 
 import android.annotation.SuppressLint
 import android.content.Context
+import android.content.res.ColorStateList
+import android.graphics.Color.TRANSPARENT
 import android.util.AttributeSet
 import android.view.LayoutInflater
-import android.widget.LinearLayout
+import android.view.MotionEvent
+import android.view.View
+import android.view.ViewConfiguration
 import androidx.annotation.DimenRes
 import androidx.annotation.DrawableRes
 import androidx.core.view.setPadding
 import com.android.launcher3.R
 import com.android.launcher3.Utilities.dpToPx
-import com.android.launcher3.config.FeatureFlags
+import com.android.launcher3.config.FeatureFlags.ENABLE_ALL_APPS_SEARCH_IN_TASKBAR
+import com.android.launcher3.config.FeatureFlags.enableTaskbarPinning
 import com.android.launcher3.taskbar.TaskbarActivityContext
+import com.android.launcher3.taskbar.TaskbarViewCallbacks
+import com.android.launcher3.util.Executors.MAIN_EXECUTOR
 import com.android.launcher3.views.ActivityContext
 import com.android.launcher3.views.IconButtonView
+import com.android.quickstep.DeviceConfigWrapper
+import com.android.quickstep.util.AssistStateManager
 
 /** Taskbar all apps button container for customizable taskbar. */
 class TaskbarAllAppsButtonContainer
@@ -38,19 +47,21 @@
     context: Context,
     attrs: AttributeSet? = null,
     defStyleAttr: Int = 0,
-) : LinearLayout(context, attrs), TaskbarContainer {
+) : IconButtonView(context, attrs), TaskbarContainer {
 
-    private val allAppsButton: IconButtonView =
-        LayoutInflater.from(context).inflate(R.layout.taskbar_all_apps_button, this, false)
-            as IconButtonView
     private val activityContext: TaskbarActivityContext = ActivityContext.lookupContext(context)
+    private var allAppsTouchTriggered = false
+    private var allAppsTouchRunnable: Runnable? = null
+    private var allAppsButtonTouchDelayMs: Long = ViewConfiguration.getLongPressTimeout().toLong()
+    private lateinit var taskbarViewCallbacks: TaskbarViewCallbacks
 
     override val spaceNeeded: Int
         get() {
-            return dpToPx(activityContext.taskbarSpecsEvaluator!!.taskbarIconSize.size.toFloat())
+            return dpToPx(activityContext.taskbarSpecsEvaluator.taskbarIconSize.size.toFloat())
         }
 
     init {
+        LayoutInflater.from(context).inflate(R.layout.taskbar_all_apps_button, null, false)
         setUpIcon()
     }
 
@@ -58,24 +69,39 @@
     private fun setUpIcon() {
         val drawable =
             resources.getDrawable(
-                getAllAppsButton(activityContext.taskbarFeatureEvaluator!!.isTransient)
+                getAllAppsButton(activityContext.taskbarFeatureEvaluator.isTransient)
             )
-        val padding = activityContext.taskbarSpecsEvaluator!!.taskbarIconPadding
+        backgroundTintList = ColorStateList.valueOf(TRANSPARENT)
+        setIconDrawable(drawable)
+        setPadding(dpToPx(activityContext.taskbarSpecsEvaluator.taskbarIconPadding.toFloat()))
+        setForegroundTint(activityContext.getColor(R.color.all_apps_button_color))
+    }
 
-        allAppsButton.setIconDrawable(drawable)
-        allAppsButton.setPadding(padding)
-        allAppsButton.setForegroundTint(activityContext.getColor(R.color.all_apps_button_color))
-
-        // TODO(b/356465292) : add click listeners in future cl
-        addView(allAppsButton)
+    @SuppressLint("ClickableViewAccessibility")
+    fun setUpCallbacks(callbacks: TaskbarViewCallbacks) {
+        taskbarViewCallbacks = callbacks
+        setOnClickListener(this::onAllAppsButtonClick)
+        setOnLongClickListener(this::onAllAppsButtonLongClick)
+        setOnTouchListener(this::onAllAppsButtonTouch)
+        isHapticFeedbackEnabled = taskbarViewCallbacks.isAllAppsButtonHapticFeedbackEnabled()
+        allAppsTouchRunnable = Runnable {
+            taskbarViewCallbacks.triggerAllAppsButtonLongClick()
+            allAppsTouchTriggered = true
+        }
+        val assistStateManager = AssistStateManager.INSTANCE[mContext]
+        if (
+            DeviceConfigWrapper.get().customLpaaThresholds &&
+                assistStateManager.lpnhDurationMillis.isPresent
+        ) {
+            allAppsButtonTouchDelayMs = assistStateManager.lpnhDurationMillis.get()
+        }
     }
 
     @DrawableRes
     private fun getAllAppsButton(isTransientTaskbar: Boolean): Int {
         val shouldSelectTransientIcon =
-            isTransientTaskbar ||
-                (FeatureFlags.enableTaskbarPinning() && !activityContext.isThreeButtonNav)
-        return if (FeatureFlags.ENABLE_ALL_APPS_SEARCH_IN_TASKBAR.get()) {
+            isTransientTaskbar || (enableTaskbarPinning() && !activityContext.isThreeButtonNav)
+        return if (ENABLE_ALL_APPS_SEARCH_IN_TASKBAR.get()) {
             if (shouldSelectTransientIcon) R.drawable.ic_transient_taskbar_all_apps_search_button
             else R.drawable.ic_taskbar_all_apps_search_button
         } else {
@@ -88,10 +114,43 @@
     fun getAllAppsButtonTranslationXOffset(isTransientTaskbar: Boolean): Int {
         return if (isTransientTaskbar) {
             R.dimen.transient_taskbar_all_apps_button_translation_x_offset
-        } else if (FeatureFlags.ENABLE_ALL_APPS_SEARCH_IN_TASKBAR.get()) {
+        } else if (ENABLE_ALL_APPS_SEARCH_IN_TASKBAR.get()) {
             R.dimen.taskbar_all_apps_search_button_translation_x_offset
         } else {
             R.dimen.taskbar_all_apps_button_translation_x_offset
         }
     }
+
+    private fun onAllAppsButtonTouch(view: View, ev: MotionEvent): Boolean {
+        when (ev.action) {
+            MotionEvent.ACTION_DOWN -> {
+                allAppsTouchTriggered = false
+                MAIN_EXECUTOR.handler.postDelayed(allAppsTouchRunnable!!, allAppsButtonTouchDelayMs)
+            }
+            MotionEvent.ACTION_UP,
+            MotionEvent.ACTION_CANCEL -> cancelAllAppsButtonTouch()
+        }
+        return false
+    }
+
+    private fun cancelAllAppsButtonTouch() {
+        MAIN_EXECUTOR.handler.removeCallbacks(allAppsTouchRunnable!!)
+        // ACTION_UP is first triggered, then click listener / long-click listener is triggered on
+        // the next frame, so we need to post twice and delay the reset.
+        this.post { this.post { allAppsTouchTriggered = false } }
+    }
+
+    private fun onAllAppsButtonClick(view: View) {
+        if (!allAppsTouchTriggered) {
+            taskbarViewCallbacks.triggerAllAppsButtonClick(view)
+        }
+    }
+
+    // Handle long click from Switch Access and Voice Access
+    private fun onAllAppsButtonLongClick(view: View): Boolean {
+        if (!MAIN_EXECUTOR.handler.hasCallbacks(allAppsTouchRunnable!!) && !allAppsTouchTriggered) {
+            taskbarViewCallbacks.triggerAllAppsButtonLongClick()
+        }
+        return true
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarDividerContainer.kt b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarDividerContainer.kt
index 26e71f7..1fb835a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarDividerContainer.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarDividerContainer.kt
@@ -18,13 +18,15 @@
 
 import android.annotation.SuppressLint
 import android.content.Context
+import android.content.res.ColorStateList
+import android.graphics.Color.TRANSPARENT
 import android.util.AttributeSet
 import android.view.LayoutInflater
-import android.widget.LinearLayout
 import androidx.core.view.setPadding
 import com.android.launcher3.R
 import com.android.launcher3.Utilities.dpToPx
 import com.android.launcher3.taskbar.TaskbarActivityContext
+import com.android.launcher3.taskbar.TaskbarViewCallbacks
 import com.android.launcher3.views.ActivityContext
 import com.android.launcher3.views.IconButtonView
 
@@ -35,31 +37,30 @@
     context: Context,
     attrs: AttributeSet? = null,
     defStyleAttr: Int = 0,
-) : LinearLayout(context, attrs), TaskbarContainer {
-
-    private val taskbarDivider: IconButtonView =
-        LayoutInflater.from(context).inflate(R.layout.taskbar_divider, this, false)
-            as IconButtonView
+) : IconButtonView(context, attrs), TaskbarContainer {
     private val activityContext: TaskbarActivityContext = ActivityContext.lookupContext(context)
 
     override val spaceNeeded: Int
         get() {
-            return dpToPx(activityContext.taskbarSpecsEvaluator!!.taskbarIconSize.size.toFloat())
+            return dpToPx(activityContext.taskbarSpecsEvaluator.taskbarIconSize.size.toFloat())
         }
 
     init {
+        LayoutInflater.from(context).inflate(R.layout.taskbar_divider, null, false)
         setUpIcon()
     }
 
     @SuppressLint("UseCompatLoadingForDrawables")
     fun setUpIcon() {
+        backgroundTintList = ColorStateList.valueOf(TRANSPARENT)
         val drawable = resources.getDrawable(R.drawable.taskbar_divider_button)
-        val padding = activityContext.taskbarSpecsEvaluator!!.taskbarIconPadding
+        setIconDrawable(drawable)
+        setPadding(dpToPx(activityContext.taskbarSpecsEvaluator.taskbarIconPadding.toFloat()))
+    }
 
-        taskbarDivider.setIconDrawable(drawable)
-        taskbarDivider.setPadding(padding)
-
-        // TODO(b/356465292):: add click listeners in future cl
-        addView(taskbarDivider)
+    @SuppressLint("ClickableViewAccessibility")
+    fun setUpCallbacks(callbacks: TaskbarViewCallbacks) {
+        setOnLongClickListener(callbacks.taskbarDividerLongClickListener)
+        setOnTouchListener(callbacks.taskbarDividerRightClickListener)
     }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarIconSpecs.kt b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarIconSpecs.kt
index 887eb01..6be0828 100644
--- a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarIconSpecs.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarIconSpecs.kt
@@ -19,10 +19,11 @@
 /** Taskbar Icon Specs */
 object TaskbarIconSpecs {
 
-    val iconSize40dp = TaskbarIconSize(40)
-    val iconSize44dp = TaskbarIconSize(44)
-    val iconSize48dp = TaskbarIconSize(48)
-    val iconSize52dp = TaskbarIconSize(52)
+    // Mapping of visual icon size to icon specs value http://b/235886078
+    val iconSize40dp = TaskbarIconSize(44)
+    val iconSize44dp = TaskbarIconSize(48)
+    val iconSize48dp = TaskbarIconSize(52)
+    val iconSize52dp = TaskbarIconSize(57)
 
     val transientTaskbarIconSizes = arrayOf(iconSize44dp, iconSize48dp, iconSize52dp)
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarSpecsEvaluator.kt b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarSpecsEvaluator.kt
index 761b47e..f37b2c1 100644
--- a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarSpecsEvaluator.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarSpecsEvaluator.kt
@@ -25,14 +25,14 @@
     numRows: Int = taskbarActivityContext.deviceProfile.inv.numRows,
     numColumns: Int = taskbarActivityContext.deviceProfile.inv.numColumns,
 ) {
-    var taskbarIconSize: TaskbarIconSize = getIconSizeByGrid(numRows, numColumns)
+    var taskbarIconSize: TaskbarIconSize = getIconSizeByGrid(numColumns, numRows)
 
     // TODO(b/341146605) : initialize it to taskbar container in later cl.
     private var taskbarContainer: List<TaskbarContainer> = emptyList()
 
     val taskbarIconPadding: Int =
-        if (TaskbarIconSpecs.minimumTaskbarIconTouchSize.size > taskbarIconSize.size) {
-            (TaskbarIconSpecs.minimumTaskbarIconTouchSize.size - taskbarIconSize.size) / 2
+        if (TaskbarIconSpecs.iconSize52dp.size > taskbarIconSize.size) {
+            (TaskbarIconSpecs.iconSize52dp.size - taskbarIconSize.size) / 2
         } else {
             0
         }
@@ -44,10 +44,10 @@
             TaskbarIconSpecs.defaultPersistentIconMargin
         }
 
-    fun getIconSizeByGrid(row: Int, column: Int): TaskbarIconSize {
+    fun getIconSizeByGrid(columns: Int, rows: Int): TaskbarIconSize {
         return if (taskbarFeatureEvaluator.isTransient) {
             TaskbarIconSpecs.transientTaskbarIconSizeByGridSize.getOrDefault(
-                TransientTaskbarIconSizeKey(row, column, taskbarFeatureEvaluator.isLandscape),
+                TransientTaskbarIconSizeKey(columns, rows, taskbarFeatureEvaluator.isLandscape),
                 TaskbarIconSpecs.defaultTransientIconSize,
             )
         } else {
@@ -102,6 +102,6 @@
 
 data class TaskbarIconSize(val size: Int)
 
-data class TransientTaskbarIconSizeKey(val row: Int, val column: Int, val isLandscape: Boolean)
+data class TransientTaskbarIconSizeKey(val columns: Int, val rows: Int, val isLandscape: Boolean)
 
 data class TaskbarIconMarginSize(val size: Int)
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
index 37e0034..4590efe 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
@@ -379,12 +379,7 @@
     }
 
     @Override
-    public void setIconVisible(boolean visible) {
-        setForceHideRing(!visible);
-        super.setIconVisible(visible);
-    }
-
-    private void setForceHideRing(boolean forceHideRing) {
+    public void setForceHideRing(boolean forceHideRing) {
         if (mForceHideRing == forceHideRing) {
             return;
         }
@@ -417,7 +412,7 @@
 
     private void drawEffect(Canvas canvas) {
         // Don't draw ring effect if item is about to be dragged or if the icon is not visible.
-        if (mDrawForDrag || !mIsIconVisible) {
+        if (mDrawForDrag || !mIsIconVisible || mForceHideRing) {
             return;
         }
         mIconRingPaint.setColor(RING_SHADOW_COLOR);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 17735e1..55c1885 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -22,7 +22,6 @@
 
 import static com.android.app.animation.Interpolators.EMPHASIZED;
 import static com.android.internal.jank.Cuj.CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_WORKSPACE;
-import static com.android.launcher3.Flags.enablePredictiveBackGesture;
 import static com.android.launcher3.Flags.enableUnfoldStateAnimation;
 import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.PENDING_SPLIT_SELECT_INFO;
 import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE;
@@ -499,10 +498,8 @@
         boolean started = ((getActivityFlags() & ACTIVITY_STATE_STARTED)) != 0;
         if (started) {
             DeviceProfile profile = getDeviceProfile();
-            boolean willUserBeActive =
-                    (getActivityFlags() & ACTIVITY_STATE_USER_WILL_BE_ACTIVE) != 0;
             boolean visible = (state == NORMAL || state == OVERVIEW)
-                    && (willUserBeActive || isUserActive())
+                    && isUserActive()
                     && !profile.isVerticalBarLayout()
                     && !mIsOverlayVisible;
             SystemUiProxy.INSTANCE.get(this)
@@ -696,9 +693,7 @@
         // Back dispatcher is registered in {@link BaseActivity#onCreate}. For predictive back to
         // work, we must opt-in BEFORE registering back dispatcher. So we need to call
         // setEnableOnBackInvokedCallback() before super.onCreate()
-        if (Utilities.ATLEAST_U && enablePredictiveBackGesture()) {
-            getApplicationInfo().setEnableOnBackInvokedCallback(true);
-        }
+        getApplicationInfo().setEnableOnBackInvokedCallback(true);
         super.onCreate(savedInstanceState);
         if (savedInstanceState != null) {
             mPendingSplitSelectInfo = ObjectWrapper.unwrap(
@@ -910,8 +905,7 @@
         // event won't go through ViewRootImpl#InputStage#onProcess.
         // So when receive back key, try to do the same check thing in
         // ViewRootImpl#NativePreImeInputStage#onProcess
-        if (!Utilities.ATLEAST_U || !enablePredictiveBackGesture()
-                || event.getKeyCode() != KeyEvent.KEYCODE_BACK
+        if (event.getKeyCode() != KeyEvent.KEYCODE_BACK
                 || event.getAction() != KeyEvent.ACTION_UP || event.isCanceled()) {
             return false;
         }
@@ -922,10 +916,6 @@
 
     @Override
     protected void registerBackDispatcher() {
-        if (!enablePredictiveBackGesture()) {
-            super.registerBackDispatcher();
-            return;
-        }
         getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
                 OnBackInvokedDispatcher.PRIORITY_DEFAULT,
                 new OnBackAnimationCallback() {
@@ -1279,10 +1269,6 @@
         return ObjectWrapper.wrap(new Integer(info.id));
     }
 
-    public void setHintUserWillBeActive() {
-        addActivityFlags(ACTIVITY_STATE_USER_WILL_BE_ACTIVE);
-    }
-
     @Override
     public void onDisplayInfoChanged(Context context, DisplayController.Info info, int flags) {
         super.onDisplayInfoChanged(context, info, flags);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
index 2625646..1ba784b 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
@@ -17,7 +17,6 @@
 
 import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
-import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
 
 import android.content.Context;
 import android.graphics.Color;
@@ -107,8 +106,7 @@
 
     @Override
     public boolean isTaskbarAlignedWithHotseat(Launcher launcher) {
-        if (ENABLE_SHELL_TRANSITIONS) return false;
-        return super.isTaskbarAlignedWithHotseat(launcher);
+        return false;
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index bbf58a2..38d08e0 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -952,7 +952,6 @@
             // We will handle the sysui flags based on the centermost task view.
             mRecentsAnimationController.setUseLauncherSystemBarFlags(swipeUpThresholdPassed
                     ||  (quickswitchThresholdPassed && centermostTaskFlags != 0));
-            mRecentsAnimationController.setSplitScreenMinimized(mContext, swipeUpThresholdPassed);
             // Provide a hint to WM the direction that we will be settling in case the animation
             // needs to be canceled
             mRecentsAnimationController.setWillFinishToHome(swipeUpThresholdPassed);
@@ -1772,8 +1771,7 @@
 
     private int calculateWindowRotation(RemoteAnimationTarget runningTaskTarget,
             RecentsOrientedState orientationState) {
-        if (runningTaskTarget.rotationChange != 0
-                && TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
+        if (runningTaskTarget.rotationChange != 0) {
             return Math.abs(runningTaskTarget.rotationChange) == ROTATION_90
                     ? ROTATION_270 : ROTATION_90;
         } else {
diff --git a/quickstep/src/com/android/quickstep/BaseContainerInterface.java b/quickstep/src/com/android/quickstep/BaseContainerInterface.java
index 3a8c141..777761b 100644
--- a/quickstep/src/com/android/quickstep/BaseContainerInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseContainerInterface.java
@@ -68,8 +68,6 @@
 
     public abstract void onAssistantVisibilityChanged(float assistantVisibility);
 
-    public abstract boolean allowMinimizeSplitScreen();
-
     public abstract boolean isResumed();
 
     public abstract boolean isStarted();
diff --git a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
index 89fbf4a..94a4527 100644
--- a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
@@ -133,12 +133,6 @@
     }
 
     @Override
-    public boolean allowMinimizeSplitScreen() {
-        // TODO: Remove this once b/77875376 is fixed
-        return false;
-    }
-
-    @Override
     public boolean allowAllAppsFromOverview() {
         return false;
     }
diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index b564fa7..e9fe2f7 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -271,11 +271,6 @@
     }
 
     @Override
-    public boolean allowMinimizeSplitScreen() {
-        return true;
-    }
-
-    @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.
diff --git a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
index d4b37f1..f653e60 100644
--- a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
+++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
@@ -107,9 +107,6 @@
                 || !mContainer.getDesktopVisibilityController().areDesktopTasksVisible());
 
         mContainer.getRootView().setForceHideBackArrow(true);
-        if (!TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
-            mContainer.setHintUserWillBeActive();
-        }
 
         if (!canUseWorkspaceView || appCanEnterPip || mIsSwipeForSplit) {
             return new LauncherHomeAnimationFactory() {
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt b/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
index d70f0d7..80ed5ae 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
@@ -33,6 +33,8 @@
 import com.android.launcher3.logging.StatsLogManager.LauncherEvent.*
 import com.android.launcher3.util.Executors
 import com.android.launcher3.util.RunnableList
+import com.android.quickstep.OverviewCommandHelper.CommandInfo.CommandStatus
+import com.android.quickstep.OverviewCommandHelper.CommandType.*
 import com.android.quickstep.util.ActiveGestureLog
 import com.android.quickstep.views.RecentsView
 import com.android.quickstep.views.RecentsViewContainer
@@ -40,6 +42,7 @@
 import com.android.systemui.shared.recents.model.ThumbnailData
 import com.android.systemui.shared.system.InteractionJankMonitorWrapper
 import java.io.PrintWriter
+import java.util.concurrent.ConcurrentLinkedDeque
 
 /** Helper class to handle various atomic commands for switching between Overview. */
 class OverviewCommandHelper(
@@ -47,7 +50,7 @@
     private val overviewComponentObserver: OverviewComponentObserver,
     private val taskAnimationManager: TaskAnimationManager
 ) {
-    private val pendingCommands = mutableListOf<CommandInfo>()
+    private val commandQueue = ConcurrentLinkedDeque<CommandInfo>()
 
     /**
      * Index of the TaskView that should be focused when launching Overview. Persisted so that we do
@@ -64,23 +67,39 @@
      */
     private var waitForToggleCommandComplete = false
 
-    /** Called when the command finishes execution. */
-    private fun scheduleNextTask(command: CommandInfo) {
-        if (pendingCommands.isEmpty()) {
-            Log.d(TAG, "no pending commands to schedule")
+    private val activityInterface: BaseActivityInterface<*, *>
+        get() = overviewComponentObserver.activityInterface
+
+    private val visibleRecentsView: RecentsView<*, *>?
+        get() = activityInterface.getVisibleRecentsView<RecentsView<*, *>>()
+
+    /**
+     * Adds a command to be executed next, after all pending tasks are completed. Max commands that
+     * can be queued is [.MAX_QUEUE_SIZE]. Requests after reaching that limit will be silently
+     * dropped.
+     */
+    @BinderThread
+    fun addCommand(type: CommandType) {
+        if (commandQueue.size >= MAX_QUEUE_SIZE) {
+            Log.d(TAG, "commands queue is full ($commandQueue). command not added: $type")
             return
         }
-        if (pendingCommands.first() !== command) {
-            Log.d(
-                TAG,
-                "next task not scheduled. First pending command type " +
-                    "is ${pendingCommands.first()} - command type is: $command"
-            )
-            return
+
+        val command = CommandInfo(type)
+        commandQueue.add(command)
+        Log.d(TAG, "command added: $command")
+
+        if (commandQueue.size == 1) {
+            Executors.MAIN_EXECUTOR.execute { executeNext() }
         }
-        Log.d(TAG, "scheduleNextTask called: $command")
-        pendingCommands.removeFirst()
-        executeNext()
+    }
+
+    fun canStartHomeSafely(): Boolean = commandQueue.isEmpty() || commandQueue.first().type == HOME
+
+    /** Clear pending commands from the queue */
+    fun clearPendingCommands() {
+        Log.d(TAG, "clearing pending commands: $commandQueue")
+        commandQueue.clear()
     }
 
     /**
@@ -90,67 +109,76 @@
      */
     @UiThread
     private fun executeNext() {
-        if (pendingCommands.isEmpty()) {
-            Log.d(TAG, "executeNext - pendingCommands is empty")
-            return
-        }
-        val command = pendingCommands.first()
-        val result = executeCommand(command)
-        Log.d(TAG, "executeNext command type: $command, result: $result")
-        if (result) {
-            scheduleNextTask(command)
-        }
-    }
+        val command: CommandInfo =
+            commandQueue.firstOrNull()
+                ?: run {
+                    Log.d(TAG, "no pending commands to be executed.")
+                    return
+                }
 
-    @UiThread
-    private fun addCommand(command: CommandInfo) {
-        val wasEmpty = pendingCommands.isEmpty()
-        pendingCommands.add(command)
-        if (wasEmpty) {
-            executeNext()
+        command.status = CommandStatus.PROCESSING
+        Log.d(TAG, "executing command: $command")
+
+        val result = executeCommand(command)
+        Log.d(TAG, "command executed: $command with result: $result")
+        if (result) {
+            onCommandFinished(command)
+        } else {
+            Log.d(TAG, "waiting for command callback: $command")
         }
     }
 
     /**
-     * Adds a command to be executed next, after all pending tasks are completed. Max commands that
-     * can be queued is [.MAX_QUEUE_SIZE]. Requests after reaching that limit will be silently
-     * dropped.
+     * Executes the task and returns true if next task can be executed. If false, then the next task
+     * is deferred until [.scheduleNextTask] is called
      */
-    @BinderThread
-    fun addCommand(type: Int) {
-        if (pendingCommands.size >= MAX_QUEUE_SIZE) {
-            Log.d(
-                TAG,
-                "the pending command queue is full (${pendingCommands.size}). command not added: $type"
-            )
-            return
+    private fun executeCommand(command: CommandInfo): Boolean {
+        if (waitForToggleCommandComplete && command.type == TOGGLE) {
+            Log.d(TAG, "executeCommand: $command - waiting for toggle command complete")
+            return true
         }
-        Log.d(TAG, "adding command type: $type")
-        val command = CommandInfo(type)
-        Executors.MAIN_EXECUTOR.execute { addCommand(command) }
-    }
 
-    @UiThread
-    fun clearPendingCommands() {
-        Log.d(TAG, "clearing pending commands - size: ${pendingCommands.size}")
-        pendingCommands.clear()
-    }
-
-    @UiThread
-    fun canStartHomeSafely(): Boolean =
-        pendingCommands.isEmpty() || pendingCommands.first().type == TYPE_HOME
-
-    private fun getNextTask(view: RecentsView<*, *>): TaskView? {
-        val runningTaskView = view.runningTaskView
-
-        return if (runningTaskView == null) {
-            view.getTaskViewAt(0)
+        val recentsView = visibleRecentsView
+        Log.d(TAG, "executeCommand: $command - visibleRecentsView: $recentsView")
+        return if (recentsView != null) {
+            executeWhenRecentsIsVisible(command, recentsView)
         } else {
-            val nextTask = view.nextTaskView
-            nextTask ?: runningTaskView
+            executeWhenRecentsIsNotVisible(command)
         }
     }
 
+    private fun executeWhenRecentsIsVisible(
+        command: CommandInfo,
+        recentsView: RecentsView<*, *>,
+    ): Boolean =
+        when (command.type) {
+            SHOW -> true // already visible
+            KEYBOARD_INPUT,
+            HIDE -> {
+                if (recentsView.isHandlingTouch) {
+                    true
+                } else {
+                    keyboardTaskFocusIndex = PagedView.INVALID_PAGE
+                    val currentPage = recentsView.nextPage
+                    val taskView = recentsView.getTaskViewAt(currentPage)
+                    launchTask(recentsView, taskView, command)
+                }
+            }
+            TOGGLE -> {
+                val taskView =
+                    if (recentsView.runningTaskView == null) {
+                        recentsView.getTaskViewAt(0)
+                    } else {
+                        recentsView.nextTaskView ?: recentsView.runningTaskView
+                    }
+                launchTask(recentsView, taskView, command)
+            }
+            HOME -> {
+                recentsView.startHome()
+                true
+            }
+        }
+
     private fun launchTask(
         recents: RecentsView<*, *>,
         taskView: TaskView?,
@@ -166,7 +194,7 @@
         if (callbackList != null) {
             callbackList.add {
                 Log.d(TAG, "launching task callback: $command")
-                scheduleNextTask(command)
+                onCommandFinished(command)
                 waitForToggleCommandComplete = false
             }
             Log.d(TAG, "launching task - waiting for callback: $command")
@@ -178,94 +206,54 @@
         }
     }
 
-    /**
-     * Executes the task and returns true if next task can be executed. If false, then the next task
-     * is deferred until [.scheduleNextTask] is called
-     */
-    private fun executeCommand(command: CommandInfo): Boolean {
-        if (waitForToggleCommandComplete && command.type == TYPE_TOGGLE) {
-            Log.d(TAG, "executeCommand: $command - waiting for toggle command complete")
-            return true
-        }
-        val activityInterface: BaseActivityInterface<*, *> =
-            overviewComponentObserver.activityInterface
+    private fun executeWhenRecentsIsNotVisible(command: CommandInfo): Boolean {
+        val recentsViewContainer = activityInterface.getCreatedContainer() as? RecentsViewContainer
+        val recentsView: RecentsView<*, *>? = recentsViewContainer?.getOverviewPanel()
+        val deviceProfile = recentsViewContainer?.getDeviceProfile()
+        val uiController = activityInterface.getTaskbarController()
+        val allowQuickSwitch =
+            FeatureFlags.ENABLE_KEYBOARD_QUICK_SWITCH.get() &&
+                uiController != null &&
+                deviceProfile != null &&
+                (deviceProfile.isTablet || deviceProfile.isTwoPanels)
 
-        val visibleRecentsView: RecentsView<*, *>? =
-            activityInterface.getVisibleRecentsView<RecentsView<*, *>>()
-        val createdRecentsView: RecentsView<*, *>?
-
-        Log.d(TAG, "executeCommand: $command - visibleRecentsView: $visibleRecentsView")
-        if (visibleRecentsView == null) {
-            val activity = activityInterface.getCreatedContainer() as? RecentsViewContainer
-            createdRecentsView = activity?.getOverviewPanel()
-            val deviceProfile = activity?.getDeviceProfile()
-            val uiController = activityInterface.getTaskbarController()
-            val allowQuickSwitch =
-                FeatureFlags.ENABLE_KEYBOARD_QUICK_SWITCH.get() &&
-                    uiController != null &&
-                    deviceProfile != null &&
-                    (deviceProfile.isTablet || deviceProfile.isTwoPanels)
-
-            when (command.type) {
-                TYPE_HIDE -> {
-                    if (!allowQuickSwitch) return true
-                    keyboardTaskFocusIndex = uiController!!.launchFocusedTask()
-                    if (keyboardTaskFocusIndex == -1) return true
-                }
-                TYPE_KEYBOARD_INPUT ->
-                    if (allowQuickSwitch) {
-                        uiController!!.openQuickSwitchView()
-                        return true
-                    } else {
-                        keyboardTaskFocusIndex = 0
-                    }
-                TYPE_HOME -> {
-                    ActiveGestureLog.INSTANCE.addLog(
-                        "OverviewCommandHelper.executeCommand(TYPE_HOME)"
-                    )
-                    // Although IActivityTaskManager$Stub$Proxy.startActivity is a slow binder call,
-                    // we should still call it on main thread because launcher is waiting for
-                    // ActivityTaskManager to resume it. Also calling startActivity() on bg thread
-                    // could potentially delay resuming launcher. See b/348668521 for more details.
-                    touchInteractionService.startActivity(overviewComponentObserver.homeIntent)
+        when (command.type) {
+            HIDE -> {
+                if (!allowQuickSwitch) return true
+                keyboardTaskFocusIndex = uiController!!.launchFocusedTask()
+                if (keyboardTaskFocusIndex == -1) return true
+            }
+            KEYBOARD_INPUT ->
+                if (allowQuickSwitch) {
+                    uiController!!.openQuickSwitchView()
                     return true
-                }
-                TYPE_SHOW ->
-                    // When Recents is not currently visible, the command's type is
-                    // TYPE_SHOW
-                    // when overview is triggered via the keyboard overview button or Action+Tab
-                    // keys (Not Alt+Tab which is KQS). The overview button on-screen in 3-button
-                    // nav is TYPE_TOGGLE.
+                } else {
                     keyboardTaskFocusIndex = 0
-                else -> {}
-            }
-        } else {
-            createdRecentsView = visibleRecentsView
-            when (command.type) {
-                TYPE_SHOW -> return true // already visible
-                TYPE_KEYBOARD_INPUT,
-                TYPE_HIDE -> {
-                    if (visibleRecentsView.isHandlingTouch) return true
-
-                    keyboardTaskFocusIndex = PagedView.INVALID_PAGE
-                    val currentPage = visibleRecentsView.nextPage
-                    val taskView = visibleRecentsView.getTaskViewAt(currentPage)
-                    return launchTask(visibleRecentsView, taskView, command)
                 }
-                TYPE_TOGGLE ->
-                    return launchTask(visibleRecentsView, getNextTask(visibleRecentsView), command)
-                TYPE_HOME -> {
-                    visibleRecentsView.startHome()
-                    return true
-                }
+            HOME -> {
+                ActiveGestureLog.INSTANCE.addLog("OverviewCommandHelper.executeCommand(HOME)")
+                // Although IActivityTaskManager$Stub$Proxy.startActivity is a slow binder call,
+                // we should still call it on main thread because launcher is waiting for
+                // ActivityTaskManager to resume it. Also calling startActivity() on bg thread
+                // could potentially delay resuming launcher. See b/348668521 for more details.
+                touchInteractionService.startActivity(overviewComponentObserver.homeIntent)
+                return true
             }
+            SHOW ->
+                // When Recents is not currently visible, the command's type is SHOW
+                // when overview is triggered via the keyboard overview button or Action+Tab
+                // keys (Not Alt+Tab which is KQS). The overview button on-screen in 3-button
+                // nav is TYPE_TOGGLE.
+                keyboardTaskFocusIndex = 0
+            TOGGLE -> {}
         }
 
-        createdRecentsView?.setKeyboardTaskFocusIndex(keyboardTaskFocusIndex)
+        recentsView?.setKeyboardTaskFocusIndex(keyboardTaskFocusIndex)
         // Handle recents view focus when launching from home
         val animatorListener: Animator.AnimatorListener =
             object : AnimatorListenerAdapter() {
                 override fun onAnimationStart(animation: Animator) {
+                    Log.d(TAG, "switching to Overview state - onAnimationStart: $command")
                     super.onAnimationStart(animation)
                     updateRecentsViewFocus(command)
                     logShowOverviewFrom(command.type)
@@ -275,7 +263,7 @@
                     Log.d(TAG, "switching to Overview state - onAnimationEnd: $command")
                     super.onAnimationEnd(animation)
                     onRecentsViewFocusUpdated(command)
-                    scheduleNextTask(command)
+                    onCommandFinished(command)
                 }
             }
         if (activityInterface.switchToRecentsIfVisible(animatorListener)) {
@@ -311,9 +299,11 @@
                     controller: RecentsAnimationController,
                     targets: RecentsAnimationTargets
                 ) {
+                    Log.d(TAG, "recents animation started: $command")
                     updateRecentsViewFocus(command)
                     logShowOverviewFrom(command.type)
                     activityInterface.runOnInitBackgroundStateUI {
+                        Log.d(TAG, "recents animation started - onInitBackgroundStateUI: $command")
                         interactionHandler.onGestureEnded(0f, PointF())
                     }
                     command.removeListener(this)
@@ -322,11 +312,12 @@
                 override fun onRecentsAnimationCanceled(
                     thumbnailDatas: HashMap<Int, ThumbnailData>
                 ) {
+                    Log.d(TAG, "recents animation canceled: $command")
                     interactionHandler.onGestureCancelled()
                     command.removeListener(this)
 
                     activityInterface.getCreatedContainer() ?: return
-                    createdRecentsView?.onRecentsAnimationComplete()
+                    recentsView?.onRecentsAnimationComplete()
                 }
             }
 
@@ -365,17 +356,29 @@
         command.removeListener(handler)
         Trace.endAsyncSection(TRANSITION_NAME, 0)
         onRecentsViewFocusUpdated(command)
-        scheduleNextTask(command)
+        onCommandFinished(command)
+    }
+
+    /** Called when the command finishes execution. */
+    private fun onCommandFinished(command: CommandInfo) {
+        command.status = CommandStatus.COMPLETED
+        if (commandQueue.first() !== command) {
+            Log.d(
+                TAG,
+                "next task not scheduled. First pending command type " +
+                    "is ${commandQueue.first()} - command type is: $command"
+            )
+            return
+        }
+
+        Log.d(TAG, "command executed successfully! $command")
+        commandQueue.remove(command)
+        executeNext()
     }
 
     private fun updateRecentsViewFocus(command: CommandInfo) {
-        val recentsView: RecentsView<*, *> =
-            overviewComponentObserver.activityInterface.getVisibleRecentsView() ?: return
-        if (
-            command.type != TYPE_KEYBOARD_INPUT &&
-                command.type != TYPE_HIDE &&
-                command.type != TYPE_SHOW
-        ) {
+        val recentsView: RecentsView<*, *> = visibleRecentsView ?: return
+        if (command.type != KEYBOARD_INPUT && command.type != HIDE && command.type != SHOW) {
             return
         }
 
@@ -387,16 +390,16 @@
         // here we launch overview with live tile.
         recentsView.viewRootImpl.touchModeChanged(false)
         // Ensure that recents view has focus so that it receives the followup key inputs
-        if (requestFocus(recentsView.getTaskViewAt(keyboardTaskFocusIndex))) return
-        if (requestFocus(recentsView.nextTaskView)) return
-        if (requestFocus(recentsView.getTaskViewAt(0))) return
-        requestFocus(recentsView)
+        // Stops requesting focused after first view gets focused.
+        recentsView.getTaskViewAt(keyboardTaskFocusIndex).requestFocus() ||
+            recentsView.nextTaskView.requestFocus() ||
+            recentsView.getTaskViewAt(0).requestFocus() ||
+            recentsView.requestFocus()
     }
 
     private fun onRecentsViewFocusUpdated(command: CommandInfo) {
-        val recentsView: RecentsView<*, *> =
-            overviewComponentObserver.activityInterface.getVisibleRecentsView() ?: return
-        if (command.type != TYPE_HIDE || keyboardTaskFocusIndex == PagedView.INVALID_PAGE) {
+        val recentsView: RecentsView<*, *> = visibleRecentsView ?: return
+        if (command.type != HIDE || keyboardTaskFocusIndex == PagedView.INVALID_PAGE) {
             return
         }
         recentsView.setKeyboardTaskFocusIndex(PagedView.INVALID_PAGE)
@@ -404,24 +407,22 @@
         keyboardTaskFocusIndex = PagedView.INVALID_PAGE
     }
 
-    private fun requestFocus(taskView: View?): Boolean {
-        if (taskView == null) return false
-        taskView.post {
-            taskView.requestFocus()
-            taskView.requestAccessibilityFocus()
+    private fun View?.requestFocus(): Boolean {
+        if (this == null) return false
+        post {
+            requestFocus()
+            requestAccessibilityFocus()
         }
         return true
     }
 
-    private fun logShowOverviewFrom(commandType: Int) {
-        val activityInterface: BaseActivityInterface<*, *> =
-            overviewComponentObserver.activityInterface
+    private fun logShowOverviewFrom(commandType: CommandType) {
         val container = activityInterface.getCreatedContainer() as? RecentsViewContainer ?: return
         val event =
             when (commandType) {
-                TYPE_SHOW -> LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_KEYBOARD_SHORTCUT
-                TYPE_HIDE -> LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_KEYBOARD_QUICK_SWITCH
-                TYPE_TOGGLE -> LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_3_BUTTON
+                SHOW -> LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_KEYBOARD_SHORTCUT
+                HIDE -> LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_KEYBOARD_QUICK_SWITCH
+                TOGGLE -> LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_3_BUTTON
                 else -> return
             }
         StatsLogManager.newInstance(container.asContext())
@@ -438,16 +439,17 @@
 
     fun dump(pw: PrintWriter) {
         pw.println("OverviewCommandHelper:")
-        pw.println("  pendingCommands=${pendingCommands.size}")
-        if (pendingCommands.isNotEmpty()) {
-            pw.println("    pendingCommandType=${pendingCommands.first().type}")
+        pw.println("  pendingCommands=${commandQueue.size}")
+        if (commandQueue.isNotEmpty()) {
+            pw.println("    pendingCommandType=${commandQueue.first().type}")
         }
-        pw.println("  mKeyboardTaskFocusIndex=$keyboardTaskFocusIndex")
-        pw.println("  mWaitForToggleCommandComplete=$waitForToggleCommandComplete")
+        pw.println("  keyboardTaskFocusIndex=$keyboardTaskFocusIndex")
+        pw.println("  waitForToggleCommandComplete=$waitForToggleCommandComplete")
     }
 
     private data class CommandInfo(
-        val type: Int,
+        val type: CommandType,
+        var status: CommandStatus = CommandStatus.IDLE,
         val createTime: Long = SystemClock.elapsedRealtime(),
         private var animationCallbacks: RecentsAnimationCallbacks? = null
     ) {
@@ -462,23 +464,30 @@
         fun removeListener(listener: RecentsAnimationCallbacks.RecentsAnimationListener?) {
             animationCallbacks?.removeListener(listener)
         }
+
+        enum class CommandStatus {
+            IDLE,
+            PROCESSING,
+            COMPLETED
+        }
+    }
+
+    enum class CommandType {
+        SHOW,
+        KEYBOARD_INPUT,
+        HIDE,
+        TOGGLE, // Navigate to Overview
+        HOME, // Navigate to Home
     }
 
     companion object {
         private const val TAG = "OverviewCommandHelper"
-
-        const val TYPE_SHOW: Int = 1
-        const val TYPE_KEYBOARD_INPUT: Int = 2
-        const val TYPE_HIDE: Int = 3
-        const val TYPE_TOGGLE: Int = 4
-        const val TYPE_HOME: Int = 5
+        private const val TRANSITION_NAME = "Transition:toOverview"
 
         /**
          * Use case for needing a queue is double tapping recents button in 3 button nav. Size of 2
          * should be enough. We'll toss in one more because we're kind hearted.
          */
         private const val MAX_QUEUE_SIZE = 3
-
-        private const val TRANSITION_NAME = "Transition:toOverview"
     }
 }
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
index 32e2389..7b9b560 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
@@ -54,17 +54,14 @@
 
     private final Set<RecentsAnimationListener> mListeners = new ArraySet<>();
     private final SystemUiProxy mSystemUiProxy;
-    private final boolean mAllowMinimizeSplitScreen;
 
     // TODO(141886704): Remove these references when they are no longer needed
     private RecentsAnimationController mController;
 
     private boolean mCancelled;
 
-    public RecentsAnimationCallbacks(SystemUiProxy systemUiProxy,
-            boolean allowMinimizeSplitScreen) {
+    public RecentsAnimationCallbacks(SystemUiProxy systemUiProxy) {
         mSystemUiProxy = systemUiProxy;
-        mAllowMinimizeSplitScreen = allowMinimizeSplitScreen;
     }
 
     @UiThread
@@ -122,7 +119,7 @@
         }
 
         mController = new RecentsAnimationController(animationController,
-                mAllowMinimizeSplitScreen, this::onAnimationFinished);
+                this::onAnimationFinished);
         if (mCancelled) {
             Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(),
                     mController::finishAnimationToApp);
@@ -219,7 +216,6 @@
     public void dump(String prefix, PrintWriter pw) {
         pw.println(prefix + "RecentsAnimationCallbacks:");
 
-        pw.println(prefix + "\tmAllowMinimizeSplitScreen=" + mAllowMinimizeSplitScreen);
         pw.println(prefix + "\tmCancelled=" + mCancelled);
     }
 
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
index 1b05e28..adcf4ef 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
@@ -17,7 +17,6 @@
 
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.FINISH_RECENTS_ANIMATION;
 
 import android.content.Context;
@@ -52,21 +51,17 @@
     private static final String TAG = "RecentsAnimationController";
     private final RecentsAnimationControllerCompat mController;
     private final Consumer<RecentsAnimationController> mOnFinishedListener;
-    private final boolean mAllowMinimizeSplitScreen;
 
     private boolean mUseLauncherSysBarFlags = false;
-    private boolean mSplitScreenMinimized = false;
     private boolean mFinishRequested = false;
     // Only valid when mFinishRequested == true.
     private boolean mFinishTargetIsLauncher;
     private RunnableList mPendingFinishCallbacks = new RunnableList();
 
     public RecentsAnimationController(RecentsAnimationControllerCompat controller,
-            boolean allowMinimizeSplitScreen,
             Consumer<RecentsAnimationController> onFinishedListener) {
         mController = controller;
         mOnFinishedListener = onFinishedListener;
-        mAllowMinimizeSplitScreen = allowMinimizeSplitScreen;
     }
 
     /**
@@ -85,34 +80,17 @@
         if (mUseLauncherSysBarFlags != useLauncherSysBarFlags) {
             mUseLauncherSysBarFlags = useLauncherSysBarFlags;
             UI_HELPER_EXECUTOR.execute(() -> {
-                if (!ENABLE_SHELL_TRANSITIONS) {
-                    mController.setAnimationTargetsBehindSystemBars(!useLauncherSysBarFlags);
-                } else {
-                    try {
-                        WindowManagerGlobal.getWindowManagerService().setRecentsAppBehindSystemBars(
-                                useLauncherSysBarFlags);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Unable to reach window manager", e);
-                    }
+                try {
+                    WindowManagerGlobal.getWindowManagerService().setRecentsAppBehindSystemBars(
+                            useLauncherSysBarFlags);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Unable to reach window manager", e);
                 }
             });
         }
     }
 
     /**
-     * Indicates that the gesture has crossed the window boundary threshold and we should minimize
-     * if we are in splitscreen.
-     */
-    public void setSplitScreenMinimized(Context context, boolean splitScreenMinimized) {
-        if (!mAllowMinimizeSplitScreen) {
-            return;
-        }
-        if (mSplitScreenMinimized != splitScreenMinimized) {
-            mSplitScreenMinimized = splitScreenMinimized;
-        }
-    }
-
-    /**
      * Remove task remote animation target from
      * {@link RecentsAnimationCallbacks#onTasksAppeared}}.
      */
@@ -272,9 +250,7 @@
     public void dump(String prefix, PrintWriter pw) {
         pw.println(prefix + "RecentsAnimationController:");
 
-        pw.println(prefix + "\tmAllowMinimizeSplitScreen=" + mAllowMinimizeSplitScreen);
         pw.println(prefix + "\tmUseLauncherSysBarFlags=" + mUseLauncherSysBarFlags);
-        pw.println(prefix + "\tmSplitScreenMinimized=" + mSplitScreenMinimized);
         pw.println(prefix + "\tmFinishRequested=" + mFinishRequested);
         pw.println(prefix + "\tmFinishTargetIsLauncher=" + mFinishTargetIsLauncher);
     }
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index 85eea3b..49ec597 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -60,9 +60,8 @@
 import java.util.HashMap;
 
 public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAnimationListener {
-    public static final boolean ENABLE_SHELL_TRANSITIONS = true;
-    public static final boolean SHELL_TRANSITIONS_ROTATION = ENABLE_SHELL_TRANSITIONS
-            && SystemProperties.getBoolean("persist.wm.debug.shell_transit_rotate", false);
+    public static final boolean SHELL_TRANSITIONS_ROTATION =
+            SystemProperties.getBoolean("persist.wm.debug.shell_transit_rotate", false);
 
     private final Context mCtx;
     private RecentsAnimationController mController;
@@ -160,8 +159,7 @@
 
         final BaseContainerInterface containerInterface = gestureState.getContainerInterface();
         mLastGestureState = gestureState;
-        RecentsAnimationCallbacks newCallbacks = new RecentsAnimationCallbacks(
-                getSystemUiProxy(), containerInterface.allowMinimizeSplitScreen());
+        RecentsAnimationCallbacks newCallbacks = new RecentsAnimationCallbacks(getSystemUiProxy());
         mCallbacks = newCallbacks;
         mCallbacks.addListener(new RecentsAnimationCallbacks.RecentsAnimationListener() {
             @Override
@@ -191,7 +189,7 @@
                 }
                 mLastGestureState.updateLastAppearedTaskTargets(mLastAppearedTaskTargets);
 
-                if (ENABLE_SHELL_TRANSITIONS && mTargets.hasRecents
+                if (mTargets.hasRecents
                         // The filtered (MODE_CLOSING) targets only contain 1 home activity.
                         && mTargets.apps.length == 1
                         && mTargets.apps[0].windowConfiguration.getActivityType()
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index b321b3e..4587bdd 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -109,6 +109,7 @@
 import com.android.launcher3.util.SafeCloseable;
 import com.android.launcher3.util.ScreenOnTracker;
 import com.android.launcher3.util.TraceHelper;
+import com.android.quickstep.OverviewCommandHelper.CommandType;
 import com.android.quickstep.inputconsumers.AccessibilityInputConsumer;
 import com.android.quickstep.inputconsumers.AssistantInputConsumer;
 import com.android.quickstep.inputconsumers.BubbleBarInputConsumer;
@@ -246,7 +247,7 @@
                     return;
                 }
                 TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
-                tis.mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_TOGGLE);
+                tis.mOverviewCommandHelper.addCommand(CommandType.TOGGLE);
             });
         }
 
@@ -256,10 +257,9 @@
             executeForTouchInteractionService(tis -> {
                 if (triggeredFromAltTab) {
                     TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
-                    tis.mOverviewCommandHelper.addCommand(
-                            OverviewCommandHelper.TYPE_KEYBOARD_INPUT);
+                    tis.mOverviewCommandHelper.addCommand(CommandType.KEYBOARD_INPUT);
                 } else {
-                    tis.mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_SHOW);
+                    tis.mOverviewCommandHelper.addCommand(CommandType.SHOW);
                 }
             });
         }
@@ -270,7 +270,7 @@
             executeForTouchInteractionService(tis -> {
                 if (triggeredFromAltTab && !triggeredFromHomeKey) {
                     // onOverviewShownFromAltTab hides the overview and ends at the target app
-                    tis.mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_HIDE);
+                    tis.mOverviewCommandHelper.addCommand(CommandType.HIDE);
                 }
             });
         }
@@ -595,12 +595,12 @@
     private final TaskbarNavButtonCallbacks mNavCallbacks = new TaskbarNavButtonCallbacks() {
         @Override
         public void onNavigateHome() {
-            mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_HOME);
+            mOverviewCommandHelper.addCommand(CommandType.HOME);
         }
 
         @Override
         public void onToggleOverview() {
-            mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_TOGGLE);
+            mOverviewCommandHelper.addCommand(CommandType.TOGGLE);
         }
     };
 
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/BubbleBarInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/BubbleBarInputConsumer.java
index dbe2068..92031c5 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/BubbleBarInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/BubbleBarInputConsumer.java
@@ -45,6 +45,7 @@
 
     private boolean mSwipeUpOnBubbleHandle;
     private boolean mPassedTouchSlop;
+    private boolean mStashedOrCollapsedOnDown;
 
     private final int mTouchSlop;
     private final PointF mDownPos = new PointF();
@@ -69,13 +70,13 @@
 
     @Override
     public void onMotionEvent(MotionEvent ev) {
-        final boolean isStashed = mBubbleStashController.isStashed();
         final int action = ev.getAction();
         switch (action) {
             case MotionEvent.ACTION_DOWN:
                 mActivePointerId = ev.getPointerId(0);
                 mDownPos.set(ev.getX(), ev.getY());
                 mLastPos.set(mDownPos);
+                mStashedOrCollapsedOnDown = mBubbleStashController.isStashed() || isCollapsed();
                 break;
             case MotionEvent.ACTION_MOVE:
                 int pointerIndex = ev.findPointerIndex(mActivePointerId);
@@ -89,7 +90,7 @@
                 if (!mPassedTouchSlop) {
                     mPassedTouchSlop = Math.abs(dY) > mTouchSlop || Math.abs(dX) > mTouchSlop;
                 }
-                if ((isCollapsed() || isStashed) && !mSwipeUpOnBubbleHandle && mPassedTouchSlop) {
+                if (mStashedOrCollapsedOnDown && !mSwipeUpOnBubbleHandle && mPassedTouchSlop) {
                     boolean verticalGesture = Math.abs(dY) > Math.abs(dX);
                     if (verticalGesture && !mBubbleDragController.isDragging()) {
                         mSwipeUpOnBubbleHandle = true;
@@ -102,11 +103,10 @@
                 break;
             case MotionEvent.ACTION_UP:
                 boolean isWithinTapTime = ev.getEventTime() - ev.getDownTime() <= mTimeForTap;
-                if (isWithinTapTime && !mSwipeUpOnBubbleHandle && !mPassedTouchSlop) {
+                if (isWithinTapTime && !mSwipeUpOnBubbleHandle && !mPassedTouchSlop
+                        && mStashedOrCollapsedOnDown) {
                     // Taps on the handle / collapsed state should open the bar
-                    if (isStashed || isCollapsed()) {
-                        mBubbleStashController.showBubbleBar(/* expandBubbles= */ true);
-                    }
+                    mBubbleStashController.showBubbleBar(/* expandBubbles= */ true);
                 }
                 break;
         }
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
index 14f47d1..b66d4cb 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
@@ -24,7 +24,6 @@
 import static com.android.quickstep.AbsSwipeUpHandler.MIN_PROGRESS_FOR_OVERVIEW;
 import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
 import static com.android.quickstep.OverviewComponentObserver.startHomeIntentSafely;
-import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
 import static com.android.quickstep.util.ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID;
 
 import android.animation.Animator;
@@ -212,15 +211,13 @@
                         // This will come back and cancel the interaction.
                         startHomeIntentSafely(mContext, mGestureState.getHomeIntent(), null, TAG);
                         mHomeLaunched = true;
-                    } else if (ENABLE_SHELL_TRANSITIONS) {
-                        if (mTaskAnimationManager.getCurrentCallbacks() != null) {
-                            if (mRecentsAnimationController != null) {
-                                finishRecentsAnimationForShell(dismissTask);
-                            } else {
-                                // the transition of recents animation hasn't started, wait for it
-                                mCancelWhenRecentsStart = true;
-                                mDismissTask = dismissTask;
-                            }
+                    } else if (mTaskAnimationManager.getCurrentCallbacks() != null) {
+                        if (mRecentsAnimationController != null) {
+                            finishRecentsAnimationForShell(dismissTask);
+                        } else {
+                            // the transition of recents animation hasn't started, wait for it
+                            mCancelWhenRecentsStart = true;
+                            mDismissTask = dismissTask;
                         }
                     }
                     mStateCallback.setState(STATE_HANDLER_INVALIDATED);
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java
index 17a97fa..9284e13 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java
@@ -46,6 +46,7 @@
 import com.android.quickstep.GestureState;
 import com.android.quickstep.InputConsumer;
 import com.android.quickstep.OverviewCommandHelper;
+import com.android.quickstep.OverviewCommandHelper.CommandType;
 import com.android.systemui.shared.system.InputMonitorCompat;
 
 /**
@@ -200,7 +201,7 @@
                         break;
                     case MotionEvent.ACTION_BUTTON_RELEASE:
                         if (isStashedTaskbarHovered) {
-                            mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_HOME);
+                            mOverviewCommandHelper.addCommand(CommandType.HOME);
                         }
                         break;
                 }
diff --git a/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
index 36ea926..acc9959 100644
--- a/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
+++ b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
@@ -41,6 +41,7 @@
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.quickstep.TouchInteractionService.TISBinder;
 import com.android.quickstep.interaction.TutorialController.TutorialType;
+import com.android.quickstep.util.LayoutUtils;
 import com.android.quickstep.util.TISBindHelper;
 
 import java.util.ArrayList;
@@ -54,8 +55,6 @@
     static final String KEY_TUTORIAL_TYPE = "tutorial_type";
     static final String KEY_GESTURE_COMPLETE = "gesture_complete";
     static final String KEY_USE_TUTORIAL_MENU = "use_tutorial_menu";
-    public static final double SQUARE_ASPECT_RATIO_BOTTOM_BOUND = 0.95;
-    public static final double SQUARE_ASPECT_RATIO_UPPER_BOUND = 1.05;
 
     @Nullable private TutorialType[] mTutorialSteps;
     private GestureSandboxFragment mCurrentFragment;
@@ -170,10 +169,7 @@
                 getApplicationContext()).getDeviceProfile(this);
         if (deviceProfile.isTablet) {
             // The tutorial will work in either orientation if the height and width are similar
-            boolean isAspectRatioSquare =
-                    deviceProfile.aspectRatio > SQUARE_ASPECT_RATIO_BOTTOM_BOUND
-                            && deviceProfile.aspectRatio < SQUARE_ASPECT_RATIO_UPPER_BOUND;
-            boolean showRotationPrompt = !isAspectRatioSquare
+            boolean showRotationPrompt = !LayoutUtils.isAspectRatioSquare(deviceProfile.aspectRatio)
                     && getResources().getConfiguration().orientation
                     == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
 
diff --git a/quickstep/src/com/android/quickstep/util/LayoutUtils.java b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
index ec1eeb1..b9338a3 100644
--- a/quickstep/src/com/android/quickstep/util/LayoutUtils.java
+++ b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
@@ -28,6 +28,8 @@
 
 public class LayoutUtils {
 
+    private static final float SQUARE_ASPECT_RATIO_TOLERANCE = 0.05f;
+
     /**
      * The height for the swipe up motion
      */
@@ -61,4 +63,13 @@
             }
         }
     }
+
+    /**
+     * Returns true iff the device's aspect ratio is within
+     * {@link LayoutUtils#SQUARE_ASPECT_RATIO_TOLERANCE} of 1:1
+     */
+    public static boolean isAspectRatioSquare(float aspectRatio) {
+        return Float.compare(aspectRatio, 1f - SQUARE_ASPECT_RATIO_TOLERANCE) >= 0
+                && Float.compare(aspectRatio, 1f + SQUARE_ASPECT_RATIO_TOLERANCE) <= 0;
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/util/RecentsViewUtils.kt b/quickstep/src/com/android/quickstep/util/RecentsViewUtils.kt
index 0a01d8b..cf08391 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsViewUtils.kt
+++ b/quickstep/src/com/android/quickstep/util/RecentsViewUtils.kt
@@ -17,9 +17,11 @@
 package com.android.quickstep.util
 
 import com.android.launcher3.Flags.enableLargeDesktopWindowingTile
+import com.android.quickstep.RecentsAnimationController
 import com.android.quickstep.views.DesktopTaskView
 import com.android.quickstep.views.TaskView
 import com.android.quickstep.views.TaskViewType
+import com.android.systemui.shared.recents.model.ThumbnailData
 
 /**
  * Helper class for [com.android.quickstep.views.RecentsView]. This util class contains refactored
@@ -68,4 +70,12 @@
             if (taskView?.isLargeTile == true) taskView else null
         }
     }
+
+    fun screenshotTasks(
+        taskView: TaskView,
+        recentsAnimationController: RecentsAnimationController
+    ): Map<Int, ThumbnailData> =
+        taskView.taskContainers.associate {
+            it.task.key.id to recentsAnimationController.screenshotTask(it.task.key.id)
+        }
 }
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index ae6757f..1af12f1 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -886,8 +886,7 @@
                 Log.w(TAG, "Package not found: " + packageName, e);
             }
             RecentsAnimationCallbacks callbacks = new RecentsAnimationCallbacks(
-                    SystemUiProxy.INSTANCE.get(mLauncher.getApplicationContext()),
-                    false /* allowMinimizeSplitScreen */);
+                    SystemUiProxy.INSTANCE.get(mLauncher.getApplicationContext()));
 
             DesktopSplitRecentsAnimationListener listener =
                     new DesktopSplitRecentsAnimationListener(splitPosition, taskBounds);
diff --git a/quickstep/src/com/android/quickstep/util/SplitWithKeyboardShortcutController.java b/quickstep/src/com/android/quickstep/util/SplitWithKeyboardShortcutController.java
index 1c417eb..4c6e4ff 100644
--- a/quickstep/src/com/android/quickstep/util/SplitWithKeyboardShortcutController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitWithKeyboardShortcutController.java
@@ -84,8 +84,7 @@
             return;
         }
         RecentsAnimationCallbacks callbacks = new RecentsAnimationCallbacks(
-                SystemUiProxy.INSTANCE.get(mLauncher.getApplicationContext()),
-                false /* allowMinimizeSplitScreen */);
+                SystemUiProxy.INSTANCE.get(mLauncher.getApplicationContext()));
         SplitWithKeyboardShortcutRecentsAnimationListener listener =
                 new SplitWithKeyboardShortcutRecentsAnimationListener(leftOrTop);
 
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index e5c54bb..c7777d8 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -24,7 +24,6 @@
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
 import static com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
-import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
 import static com.android.quickstep.util.RecentsOrientedState.postDisplayRotation;
 import static com.android.quickstep.util.RecentsOrientedState.preDisplayRotation;
 
@@ -535,21 +534,12 @@
 
         // If mDrawsBelowRecents is unset, no reordering will be enforced.
         if (mDrawsBelowRecents != null) {
-            // In legacy transitions, the animation leashes remain in same hierarchy in the
-            // TaskDisplayArea, so we don't want to bump the layer too high otherwise it will
-            // conflict with layers that WM core positions (ie. the input consumers).  For shell
-            // transitions, the animation leashes are reparented to an animation container so we
-            // can bump layers as needed.
-            if (ENABLE_SHELL_TRANSITIONS) {
-                builder.setLayer(mDrawsBelowRecents
-                        ? Integer.MIN_VALUE + app.prefixOrderIndex
-                        // 1000 is an arbitrary number to give room for multiple layers.
-                        : Integer.MAX_VALUE - 1000 + app.prefixOrderIndex);
-            } else {
-                builder.setLayer(mDrawsBelowRecents
-                        ? Integer.MIN_VALUE + app.prefixOrderIndex
-                        : 0);
-            }
+            // In shell transitions, the animation leashes are reparented to an animation container
+            // so we can bump layers as needed.
+            builder.setLayer(mDrawsBelowRecents
+                    ? Integer.MIN_VALUE + app.prefixOrderIndex
+                    // 1000 is an arbitrary number to give room for multiple layers.
+                    : Integer.MAX_VALUE - 1000 + app.prefixOrderIndex);
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/FloatingWidgetView.java b/quickstep/src/com/android/quickstep/views/FloatingWidgetView.java
index 4dde635..bdca596 100644
--- a/quickstep/src/com/android/quickstep/views/FloatingWidgetView.java
+++ b/quickstep/src/com/android/quickstep/views/FloatingWidgetView.java
@@ -123,8 +123,8 @@
     @Override
     public void onGlobalLayout() {
         if (isUninitialized()) return;
-        positionViews();
-        if (mOnTargetChangeRunnable != null) {
+        boolean positionsChanged = positionViews();
+        if (mOnTargetChangeRunnable != null && positionsChanged) {
             mOnTargetChangeRunnable.run();
         }
     }
@@ -212,21 +212,43 @@
         onGlobalLayout();
     }
 
-    /** Sets the layout parameters of the floating view and its background view child. */
-    private void positionViews() {
+    /**
+     * Sets the layout parameters of the floating view and its background view child.
+     * @return true if any of the views positions change due to this call.
+     */
+    private boolean positionViews() {
+        boolean positionsChanged = false;
+
         LayoutParams layoutParams = (LayoutParams) getLayoutParams();
-        layoutParams.setMargins(0, 0, 0, 0);
-        setLayoutParams(layoutParams);
+
+        if (layoutParams.topMargin != 0 || layoutParams.bottomMargin != 0
+                || layoutParams.rightMargin != 0 || layoutParams.leftMargin != 0) {
+            positionsChanged = true;
+            layoutParams.setMargins(0, 0, 0, 0);
+            setLayoutParams(layoutParams);
+        }
 
         // FloatingWidgetView layout is forced LTR
-        mBackgroundView.setTranslationX(mBackgroundPosition.left);
-        mBackgroundView.setTranslationY(mBackgroundPosition.top + mIconOffsetY);
+        float targetY = mBackgroundPosition.top + mIconOffsetY;
+        if (mBackgroundView.getTranslationX() != mBackgroundPosition.left
+                || mBackgroundView.getTranslationY() != targetY) {
+            positionsChanged = true;
+            mBackgroundView.setTranslationX(mBackgroundPosition.left);
+            mBackgroundView.setTranslationY(targetY);
+        }
+
         LayoutParams backgroundParams = (LayoutParams) mBackgroundView.getLayoutParams();
-        backgroundParams.leftMargin = 0;
-        backgroundParams.topMargin = 0;
-        backgroundParams.width = (int) mBackgroundPosition.width();
-        backgroundParams.height = (int) mBackgroundPosition.height();
-        mBackgroundView.setLayoutParams(backgroundParams);
+        if (backgroundParams.leftMargin != 0 || backgroundParams.topMargin != 0
+                || backgroundParams.width != Math.round(mBackgroundPosition.width())
+                || backgroundParams.height != Math.round(mBackgroundPosition.height())) {
+            positionsChanged = true;
+
+            backgroundParams.leftMargin = 0;
+            backgroundParams.topMargin = 0;
+            backgroundParams.width = Math.round(mBackgroundPosition.width());
+            backgroundParams.height = Math.round(mBackgroundPosition.height());
+            mBackgroundView.setLayoutParams(backgroundParams);
+        }
 
         if (mForegroundOverlayView != null) {
             sTmpMatrix.reset();
@@ -237,8 +259,15 @@
             sTmpMatrix.postScale(foregroundScale, foregroundScale);
             sTmpMatrix.postTranslate(mBackgroundPosition.left, mBackgroundPosition.top
                     + mIconOffsetY);
-            mForegroundOverlayView.setMatrix(sTmpMatrix);
+
+            // We use the animation matrix here, because calling setMatrix on the GhostView
+            // actually sets the animation matrix, not the regular one.
+            if (!sTmpMatrix.equals(mForegroundOverlayView.getAnimationMatrix())) {
+                positionsChanged = true;
+                mForegroundOverlayView.setMatrix(sTmpMatrix);
+            }
         }
+        return positionsChanged;
     }
 
     private void finish(DragLayer dragLayer) {
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 255619a..226ecf5 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -3058,14 +3058,15 @@
     }
 
     private void setRunningTaskViewShowScreenshot(boolean showScreenshot) {
+        setRunningTaskViewShowScreenshot(showScreenshot, /*updatedThumbnails=*/null);
+    }
+
+    private void setRunningTaskViewShowScreenshot(boolean showScreenshot,
+            @Nullable Map<Integer, ThumbnailData> updatedThumbnails) {
         mRunningTaskShowScreenshot = showScreenshot;
         TaskView runningTaskView = getRunningTaskView();
         if (runningTaskView != null) {
-            runningTaskView.setShouldShowScreenshot(mRunningTaskShowScreenshot);
-            if (!enableRefactorTaskThumbnail()) {
-                runningTaskView.getTaskContainers().forEach(
-                        taskContainer -> taskContainer.getThumbnailViewDeprecated().refresh());
-            }
+            runningTaskView.setShouldShowScreenshot(mRunningTaskShowScreenshot, updatedThumbnails);
         }
         if (enableRefactorTaskThumbnail()) {
             mRecentsViewModel.setRunningTaskShowScreenshot(showScreenshot);
@@ -6154,20 +6155,12 @@
             return;
         }
 
+        Map<Integer, ThumbnailData> updatedThumbnails = mRecentsViewUtils.screenshotTasks(taskView,
+                mRecentsAnimationController);
         if (enableRefactorTaskThumbnail()) {
-            mHelper.switchToScreenshot(taskView, mRecentsAnimationController, onFinishRunnable);
+            mHelper.switchToScreenshot(taskView, updatedThumbnails, onFinishRunnable);
         } else {
-            setRunningTaskViewShowScreenshot(true);
-            for (TaskContainer container : taskView.getTaskContainers()) {
-                ThumbnailData thumbnailData =
-                        mRecentsAnimationController.screenshotTask(container.getTask().key.id);
-                TaskThumbnailViewDeprecated thumbnailView = container.getThumbnailViewDeprecated();
-                if (thumbnailData != null) {
-                    thumbnailView.setThumbnail(container.getTask(), thumbnailData);
-                } else {
-                    thumbnailView.refresh();
-                }
-            }
+            setRunningTaskViewShowScreenshot(true, updatedThumbnails);
             ViewUtils.postFrameDrawn(taskView, onFinishRunnable);
         }
     }
@@ -6186,8 +6179,7 @@
             if (enableRefactorTaskThumbnail()) {
                 mHelper.switchToScreenshot(taskView, thumbnailDatas, onFinishRunnable);
             } else {
-                taskView.setShouldShowScreenshot(true);
-                taskView.refreshThumbnails(thumbnailDatas);
+                taskView.setShouldShowScreenshot(true, thumbnailDatas);
                 ViewUtils.postFrameDrawn(taskView, onFinishRunnable);
             }
         } else {
diff --git a/quickstep/src/com/android/quickstep/views/RecentsViewModelHelper.kt b/quickstep/src/com/android/quickstep/views/RecentsViewModelHelper.kt
index f5b2176..4604b70 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsViewModelHelper.kt
+++ b/quickstep/src/com/android/quickstep/views/RecentsViewModelHelper.kt
@@ -16,7 +16,6 @@
 
 package com.android.quickstep.views
 
-import com.android.quickstep.RecentsAnimationController
 import com.android.quickstep.ViewUtils
 import com.android.quickstep.recents.viewmodel.RecentsViewModel
 import com.android.systemui.shared.recents.model.ThumbnailData
@@ -42,18 +41,6 @@
 
     fun switchToScreenshot(
         taskView: TaskView,
-        recentsAnimationController: RecentsAnimationController,
-        onFinishRunnable: Runnable,
-    ) {
-        val updatedThumbnails =
-            taskView.taskContainers.associate {
-                it.task.key.id to recentsAnimationController.screenshotTask(it.task.key.id)
-            }
-        switchToScreenshot(taskView, updatedThumbnails, onFinishRunnable)
-    }
-
-    fun switchToScreenshot(
-        taskView: TaskView,
         updatedThumbnails: Map<Int, ThumbnailData>?,
         onFinishRunnable: Runnable,
     ) {
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index 815f8fa..d2cdfa2 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -79,7 +79,6 @@
 import com.android.launcher3.views.ActivityContext
 import com.android.quickstep.RecentsModel
 import com.android.quickstep.RemoteAnimationTargets
-import com.android.quickstep.TaskAnimationManager
 import com.android.quickstep.TaskOverlayFactory
 import com.android.quickstep.TaskViewUtils
 import com.android.quickstep.orientation.RecentsPagedOrientationHandler
@@ -408,6 +407,7 @@
 
     protected var shouldShowScreenshot = false
         get() = !isRunningTask || field
+        private set
 
     /** Enable or disable showing border on hover and focus change */
     @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
@@ -974,7 +974,13 @@
         iconView.setText(text)
     }
 
-    open fun refreshThumbnails(thumbnailDatas: Map<Int, ThumbnailData?>?) {
+    @JvmOverloads
+    open fun setShouldShowScreenshot(
+        shouldShowScreenshot: Boolean,
+        thumbnailDatas: Map<Int, ThumbnailData?>? = null
+    ) {
+        if (this.shouldShowScreenshot == shouldShowScreenshot) return
+        this.shouldShowScreenshot = shouldShowScreenshot
         if (enableRefactorTaskThumbnail()) {
             return
         }
@@ -1041,14 +1047,12 @@
                 // triggered by QuickstepTransitionManager.AppLaunchAnimationRunner.
                 return RunnableList().also { recentsView.addSideTaskLaunchCallback(it) }
             }
-            if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
-                // If the recents transition is running (ie. in live tile mode), then the start
-                // of a new task will merge into the existing transition and it currently will
-                // not be run independently, so we need to rely on the onTaskAppeared() call
-                // for the new task to trigger the side launch callback to flush this runnable
-                // list (which is usually flushed when the app launch animation finishes)
-                recentsView.addSideTaskLaunchCallback(opts.onEndCallback)
-            }
+            // If the recents transition is running (ie. in live tile mode), then the start
+            // of a new task will merge into the existing transition and it currently will
+            // not be run independently, so we need to rely on the onTaskAppeared() call
+            // for the new task to trigger the side launch callback to flush this runnable
+            // list (which is usually flushed when the app launch animation finishes)
+            recentsView.addSideTaskLaunchCallback(opts.onEndCallback)
             return opts.onEndCallback
         } else {
             notifyTaskLaunchFailed()
diff --git a/quickstep/tests/multivalentScreenshotTests/src/com/android/launcher3/taskbar/bubbles/BubbleViewScreenshotTest.kt b/quickstep/tests/multivalentScreenshotTests/src/com/android/launcher3/taskbar/bubbles/BubbleViewScreenshotTest.kt
index d77ac5c..1d92d7e 100644
--- a/quickstep/tests/multivalentScreenshotTests/src/com/android/launcher3/taskbar/bubbles/BubbleViewScreenshotTest.kt
+++ b/quickstep/tests/multivalentScreenshotTests/src/com/android/launcher3/taskbar/bubbles/BubbleViewScreenshotTest.kt
@@ -99,7 +99,7 @@
         val flags =
             if (suppressNotification) Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION else 0
         val bubbleInfo =
-            BubbleInfo("key", flags, null, null, 0, context.packageName, null, null, false)
+            BubbleInfo("key", flags, null, null, 0, context.packageName, null, null, false, true)
         val bubbleView = inflater.inflate(R.layout.bubblebar_item_view, null) as BubbleView
         val dotPath =
             PathParser.createPathFromPathData(
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/BubbleViewTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/BubbleViewTest.kt
index 8bad3b9..cb5488c 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/BubbleViewTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/BubbleViewTest.kt
@@ -65,7 +65,7 @@
             overflowView.setOverflow(BubbleBarOverflow(overflowView), bitmap)
 
             val bubbleInfo =
-                BubbleInfo("key", 0, null, null, 0, context.packageName, null, null, false)
+                BubbleInfo("key", 0, null, null, 0, context.packageName, null, null, false, true)
             bubbleView = inflater.inflate(R.layout.bubblebar_item_view, null, false) as BubbleView
             bubble =
                 BubbleBarBubble(bubbleInfo, bubbleView, bitmap, bitmap, Color.WHITE, Path(), "")
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt
index 7928ce9..2bca74a 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt
@@ -870,7 +870,7 @@
             bubbleBarView.addView(overflowView)
 
             val bubbleInfo =
-                BubbleInfo("key", 0, null, null, 0, context.packageName, null, null, false)
+                BubbleInfo("key", 0, null, null, 0, context.packageName, null, null, false, true)
             bubbleView =
                 inflater.inflate(R.layout.bubblebar_item_view, bubbleBarView, false) as BubbleView
             bubble =
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentBubbleStashControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentBubbleStashControllerTest.kt
index c0a5dfa..4106a2c 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentBubbleStashControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentBubbleStashControllerTest.kt
@@ -251,7 +251,7 @@
 
         whenever(bubbleBarViewController.hasBubbles()).thenReturn(true)
         whenever(bubbleBarViewController.bubbleBarTranslationY).thenReturn(translationY)
-        whenever(bubbleBarViewController.bubbleBarScale).thenReturn(scale)
+        whenever(bubbleBarViewController.bubbleBarScaleY).thenReturn(scale)
         whenever(bubbleBarViewController.bubbleBarAlpha).thenReturn(alpha)
         whenever(bubbleBarViewController.bubbleBarCollapsedHeight).thenReturn(BUBBLE_BAR_HEIGHT)
     }
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 b5809c2..63c2197 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
@@ -31,7 +31,6 @@
 import com.android.launcher3.taskbar.bubbles.BubbleBarView
 import com.android.launcher3.taskbar.bubbles.BubbleBarViewController
 import com.android.launcher3.taskbar.bubbles.BubbleStashedHandleViewController
-import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.Companion.STASHED_BAR_SCALE
 import com.android.launcher3.util.MultiValueAlpha
 import com.android.wm.shell.shared.animation.PhysicsAnimator
 import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils
@@ -59,7 +58,7 @@
         const val HOTSEAT_TRANSLATION_Y = -45f
         const val TASK_BAR_TRANSLATION_Y = -TASKBAR_BOTTOM_SPACE
         const val HANDLE_VIEW_HEIGHT = 4
-        const val BUBBLE_BAR_STASHED_TRANSLATION_Y = 48
+        const val BUBBLE_BAR_STASHED_TRANSLATION_Y = -2.5f
     }
 
     @get:Rule val animatorTestRule: AnimatorTestRule = AnimatorTestRule(this)
@@ -90,7 +89,7 @@
         val taskbarHotseatDimensionsProvider =
             DefaultDimensionsProvider(taskBarBottomSpace = TASKBAR_BOTTOM_SPACE)
         mTransientBubbleStashController =
-            TransientBubbleStashController(taskbarHotseatDimensionsProvider, context.resources)
+            TransientBubbleStashController(taskbarHotseatDimensionsProvider, context)
         setUpBubbleBarView()
         setUpBubbleBarController()
         setUpStashedHandleView()
@@ -174,8 +173,8 @@
         assertThat(mTransientBubbleStashController.isStashed).isTrue()
         assertThat(bubbleBarView.translationY).isEqualTo(BUBBLE_BAR_STASHED_TRANSLATION_Y)
         assertThat(bubbleBarView.alpha).isEqualTo(0f)
-        assertThat(bubbleBarView.scaleX).isEqualTo(STASHED_BAR_SCALE)
-        assertThat(bubbleBarView.scaleY).isEqualTo(STASHED_BAR_SCALE)
+        assertThat(bubbleBarView.scaleX).isEqualTo(mTransientBubbleStashController.getStashScale())
+        assertThat(bubbleBarView.scaleY).isEqualTo(mTransientBubbleStashController.getStashScale())
         // Handle view is visible
         assertThat(stashedHandleView.translationY).isEqualTo(0)
         assertThat(stashedHandleView.alpha).isEqualTo(1)
@@ -243,8 +242,8 @@
         // Then all property values are updated
         assertThat(bubbleBarView.translationY).isEqualTo(BUBBLE_BAR_STASHED_TRANSLATION_Y)
         assertThat(bubbleBarView.alpha).isEqualTo(0)
-        assertThat(bubbleBarView.scaleX).isEqualTo(STASHED_BAR_SCALE)
-        assertThat(bubbleBarView.scaleY).isEqualTo(STASHED_BAR_SCALE)
+        assertThat(bubbleBarView.scaleX).isEqualTo(mTransientBubbleStashController.getStashScale())
+        assertThat(bubbleBarView.scaleY).isEqualTo(mTransientBubbleStashController.getStashScale())
         // Handle is visible at correct Y position
         assertThat(stashedHandleView.alpha).isEqualTo(1)
         assertThat(stashedHandleView.translationY).isEqualTo(0)
@@ -306,7 +305,7 @@
 
         whenever(bubbleBarViewController.hasBubbles()).thenReturn(true)
         whenever(bubbleBarViewController.bubbleBarTranslationY).thenReturn(barTranslationY)
-        whenever(bubbleBarViewController.bubbleBarScale).thenReturn(barScale)
+        whenever(bubbleBarViewController.bubbleBarScaleY).thenReturn(barScale)
         whenever(bubbleBarViewController.bubbleBarAlpha).thenReturn(barAlpha)
         whenever(bubbleBarViewController.bubbleBarCollapsedHeight).thenReturn(BUBBLE_BAR_HEIGHT)
     }
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 597227a..f971afb 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -266,9 +266,6 @@
         return launcher.<RecentsView>getOverviewPanel().getBottomRowTaskCountForTablet();
     }
 
-    // Staging; will be promoted to presubmit if stable
-    @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT)
-
     @Test
     @NavigationModeSwitch
     @PortraitLandscape
diff --git a/quickstep/tests/src/com/android/quickstep/TaskAnimationManagerTest.java b/quickstep/tests/src/com/android/quickstep/TaskAnimationManagerTest.java
index 3a83ae3..28c8a4a 100644
--- a/quickstep/tests/src/com/android/quickstep/TaskAnimationManagerTest.java
+++ b/quickstep/tests/src/com/android/quickstep/TaskAnimationManagerTest.java
@@ -59,8 +59,6 @@
 
     @Test
     public void startRecentsActivity_allowBackgroundLaunch() {
-        assumeTrue(TaskAnimationManager.ENABLE_SHELL_TRANSITIONS);
-
         final LauncherActivityInterface activityInterface = mock(LauncherActivityInterface.class);
         final GestureState gestureState = mock(GestureState.class);
         final RecentsAnimationCallbacks.RecentsAnimationListener listener =
diff --git a/res/values/styles.xml b/res/values/styles.xml
index ee7ed26..2d7808b 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -77,6 +77,10 @@
         <item name="materialColorError">@color/system_error_light</item>
     </style>
 
+    <style name="DynamicColorsBaseLauncherTheme.NoActionBar">
+        <item name="windowActionBar">false</item>
+        <item name="windowNoTitle">true</item>
+    </style>
 
     <style name="LauncherTheme" parent="@style/DynamicColorsBaseLauncherTheme">
         <item name="android:textColorSecondary">#DE000000</item>
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index 633091d..fec94fe 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -110,11 +110,6 @@
     public static final int ACTIVITY_STATE_USER_ACTIVE = 1 << 4;
 
     /**
-     * State flag indicating if the user will be active shortly.
-     */
-    public static final int ACTIVITY_STATE_USER_WILL_BE_ACTIVE = 1 << 5;
-
-    /**
      * State flag indicating that a state transition is in progress
      */
     public static final int ACTIVITY_STATE_TRANSITION_ACTIVE = 1 << 6;
@@ -316,7 +311,6 @@
      */
     public void setResumed() {
         addActivityFlags(ACTIVITY_STATE_RESUMED | ACTIVITY_STATE_USER_ACTIVE);
-        removeActivityFlags(ACTIVITY_STATE_USER_WILL_BE_ACTIVE);
     }
 
     public boolean isUserActive() {
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 26e900d..8121e53 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -89,7 +89,7 @@
 import com.android.launcher3.util.ShortcutUtil;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.ActivityContext;
-import com.android.launcher3.views.IconLabelDotView;
+import com.android.launcher3.views.FloatingIconViewCompanion;
 
 import java.text.NumberFormat;
 import java.util.HashMap;
@@ -101,7 +101,7 @@
  * too aggressive.
  */
 public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
-        IconLabelDotView, DraggableView, Reorderable {
+        FloatingIconViewCompanion, DraggableView, Reorderable {
 
     public static final String TAG = "BubbleTextView";
 
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 3ca6099..bafb528 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -435,6 +435,10 @@
         mIsColdStartupAfterReboot = sIsNewProcess
             && !LockedUserState.get(this).isUserUnlockedAtLauncherStartup();
         if (mIsColdStartupAfterReboot) {
+            /*
+             * This trace is used to calculate the time from create to the point that icons are
+             * visible.
+             */
             Trace.beginAsyncSection(
                     COLD_STARTUP_TRACE_METHOD_NAME, COLD_STARTUP_TRACE_COOKIE);
         }
@@ -2384,10 +2388,6 @@
                     .logEnd(LAUNCHER_LATENCY_STARTUP_TOTAL_DURATION)
                     .log()
                     .reset();
-            if (mIsColdStartupAfterReboot) {
-                Trace.endAsyncSection(COLD_STARTUP_TRACE_METHOD_NAME,
-                        COLD_STARTUP_TRACE_COOKIE);
-            }
         });
     }
 
@@ -2396,6 +2396,10 @@
             RunnableList onCompleteSignal, int workspaceItemCount, boolean isBindSync) {
         mModelCallbacks.onInitialBindComplete(boundPages, pendingTasks, onCompleteSignal,
                 workspaceItemCount, isBindSync);
+        if (mIsColdStartupAfterReboot) {
+            Trace.endAsyncSection(COLD_STARTUP_TRACE_METHOD_NAME,
+                    COLD_STARTUP_TRACE_COOKIE);
+        }
     }
 
     /**
diff --git a/src/com/android/launcher3/debug/TestEventsEmitterProduction.kt b/src/com/android/launcher3/debug/TestEventsEmitterProduction.kt
index e218b4d..52b454f 100644
--- a/src/com/android/launcher3/debug/TestEventsEmitterProduction.kt
+++ b/src/com/android/launcher3/debug/TestEventsEmitterProduction.kt
@@ -27,6 +27,8 @@
     WORKSPACE_ON_DROP("WORKSPACE_ON_DROP"),
     RESIZE_FRAME_SHOWING("RESIZE_FRAME_SHOWING"),
     WORKSPACE_FINISH_LOADING("WORKSPACE_FINISH_LOADING"),
+    SPRING_LOADED_STATE_STARTED("SPRING_LOADED_STATE_STARTED"),
+    SPRING_LOADED_STATE_COMPLETED("SPRING_LOADED_STATE_COMPLETED"),
 }
 
 /** Interface to create TestEventEmitters. */
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index a0b695a..de1bcc3 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -83,7 +83,7 @@
 import com.android.launcher3.util.MultiTranslateDelegate;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.views.ActivityContext;
-import com.android.launcher3.views.IconLabelDotView;
+import com.android.launcher3.views.FloatingIconViewCompanion;
 import com.android.launcher3.widget.PendingAddShortcutInfo;
 
 import java.util.ArrayList;
@@ -93,7 +93,7 @@
 /**
  * An icon that can appear on in the workspace representing an {@link Folder}.
  */
-public class FolderIcon extends FrameLayout implements FolderListener, IconLabelDotView,
+public class FolderIcon extends FrameLayout implements FolderListener, FloatingIconViewCompanion,
         DraggableView, Reorderable {
 
     private final MultiTranslateDelegate mTranslateDelegate = new MultiTranslateDelegate(this);
diff --git a/src/com/android/launcher3/views/BubbleTextHolder.java b/src/com/android/launcher3/views/BubbleTextHolder.java
index 84f8049..d2ae93b 100644
--- a/src/com/android/launcher3/views/BubbleTextHolder.java
+++ b/src/com/android/launcher3/views/BubbleTextHolder.java
@@ -20,7 +20,7 @@
 /**
  * Views that contain {@link BubbleTextView} should implement this interface.
  */
-public interface BubbleTextHolder extends IconLabelDotView {
+public interface BubbleTextHolder extends FloatingIconViewCompanion {
     BubbleTextView getBubbleText();
 
     @Override
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index 37482ac..4ee6aff 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -22,7 +22,7 @@
 import static com.android.launcher3.Utilities.getFullDrawable;
 import static com.android.launcher3.Utilities.mapToRange;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
-import static com.android.launcher3.views.IconLabelDotView.setIconAndDotVisible;
+import static com.android.launcher3.views.FloatingIconViewCompanion.setPropertiesVisible;
 
 import android.animation.Animator;
 import android.content.Context;
@@ -516,6 +516,10 @@
             // When closing an app, we want the item on the workspace to be invisible immediately
             updateViewsVisibility(false  /* isVisible */);
         }
+        if (mFadeOutView instanceof FloatingIconViewCompanion fivc) {
+            fivc.setForceHideDot(true);
+            fivc.setForceHideRing(true);
+        }
     }
 
     @Override
@@ -653,6 +657,10 @@
             if (view.mFadeOutView != null) {
                 view.mFadeOutView.setAlpha(1f);
             }
+            if (view.mFadeOutView instanceof FloatingIconViewCompanion fivc) {
+                fivc.setForceHideDot(false);
+                fivc.setForceHideRing(false);
+            }
 
             if (hideOriginal) {
                 view.updateViewsVisibility(true /* isVisible */);
@@ -674,10 +682,10 @@
 
     private void updateViewsVisibility(boolean isVisible) {
         if (mOriginalIcon != null) {
-            setIconAndDotVisible(mOriginalIcon, isVisible);
+            setPropertiesVisible(mOriginalIcon, isVisible);
         }
         if (mMatchVisibilityView != null) {
-            setIconAndDotVisible(mMatchVisibilityView, isVisible);
+            setPropertiesVisible(mMatchVisibilityView, isVisible);
         }
     }
 
diff --git a/src/com/android/launcher3/views/FloatingIconViewCompanion.java b/src/com/android/launcher3/views/FloatingIconViewCompanion.java
new file mode 100644
index 0000000..fc23903
--- /dev/null
+++ b/src/com/android/launcher3/views/FloatingIconViewCompanion.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2019 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.views;
+
+import android.view.View;
+
+/**
+ * A view that can be drawn (in some capacity) via) {@link FloatingIconView}.
+ * This interface allows us to hide certain properties of the view that the FloatingIconView
+ * cannot draw, which allows us to make a seamless handoff between the FloatingIconView and
+ * the companion view.
+ */
+public interface FloatingIconViewCompanion {
+    void setIconVisible(boolean visible);
+    void setForceHideDot(boolean hide);
+    default void setForceHideRing(boolean hide) {}
+
+    /**
+     * Sets the visibility of icon and dot of the view
+     */
+    static void setPropertiesVisible(View view, boolean visible) {
+        if (view instanceof FloatingIconViewCompanion) {
+            ((FloatingIconViewCompanion) view).setIconVisible(visible);
+            ((FloatingIconViewCompanion) view).setForceHideDot(!visible);
+            ((FloatingIconViewCompanion) view).setForceHideRing(!visible);
+        } else {
+            view.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
+        }
+    }
+}
diff --git a/src/com/android/launcher3/views/FloatingSurfaceView.java b/src/com/android/launcher3/views/FloatingSurfaceView.java
index cab7982..7fa7517 100644
--- a/src/com/android/launcher3/views/FloatingSurfaceView.java
+++ b/src/com/android/launcher3/views/FloatingSurfaceView.java
@@ -17,7 +17,7 @@
 
 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.IconLabelDotView.setIconAndDotVisible;
+import static com.android.launcher3.views.FloatingIconViewCompanion.setPropertiesVisible;
 
 import android.content.Context;
 import android.graphics.Canvas;
@@ -237,7 +237,7 @@
 
     private void setCurrentIconVisible(boolean isVisible) {
         if (mIcon != null) {
-            setIconAndDotVisible(mIcon, isVisible);
+            setPropertiesVisible(mIcon, isVisible);
         }
     }
 }
diff --git a/src/com/android/launcher3/views/IconLabelDotView.java b/src/com/android/launcher3/views/IconLabelDotView.java
deleted file mode 100644
index e9113cf..0000000
--- a/src/com/android/launcher3/views/IconLabelDotView.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2019 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.views;
-
-import android.view.View;
-
-/**
- * A view that has an icon, label, and notification dot.
- */
-public interface IconLabelDotView {
-    void setIconVisible(boolean visible);
-    void setForceHideDot(boolean hide);
-
-    /**
-     * Sets the visibility of icon and dot of the view
-     */
-    static void setIconAndDotVisible(View view, boolean visible) {
-        if (view instanceof IconLabelDotView) {
-            ((IconLabelDotView) view).setIconVisible(visible);
-            ((IconLabelDotView) view).setForceHideDot(!visible);
-        } else {
-            view.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
-        }
-    }
-}
diff --git a/tests/Android.bp b/tests/Android.bp
index 1fa6e05..9f62d02 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -127,9 +127,9 @@
         "com_android_launcher3_flags_lib",
     ],
     libs: [
-        "android.test.base",
-        "android.test.runner",
-        "android.test.mock",
+        "android.test.base.stubs.system",
+        "android.test.runner.stubs.system",
+        "android.test.mock.stubs.system",
     ],
     // Libraries used by mockito inline extended
     jni_libs: [
@@ -229,9 +229,9 @@
         "android.appwidget.flags-aconfig-java",
     ],
     libs: [
-        "android.test.runner",
-        "android.test.base",
-        "android.test.mock",
+        "android.test.runner.stubs.system",
+        "android.test.base.stubs.system",
+        "android.test.mock.stubs.system",
         "truth",
     ],
     instrumentation_for: "Launcher3",
diff --git a/tests/src/com/android/launcher3/ui/widget/TaplAddWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/TaplAddWidgetTest.java
index 9b184ae..9c916fa 100644
--- a/tests/src/com/android/launcher3/ui/widget/TaplAddWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/TaplAddWidgetTest.java
@@ -15,9 +15,6 @@
  */
 package com.android.launcher3.ui.widget;
 
-import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
-import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
-
 import static org.junit.Assert.assertNotNull;
 
 import android.platform.test.annotations.PlatinumTest;
@@ -33,7 +30,6 @@
 import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
 import com.android.launcher3.ui.TestViewHelpers;
 import com.android.launcher3.util.rule.ShellCommandRule;
-import com.android.launcher3.util.rule.TestStabilityRule.Stability;
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
 
 import org.junit.Assume;
@@ -102,7 +98,6 @@
     /**
      * Test dragging a widget to the workspace and resize it.
      */
-    @Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/316910614
     @PlatinumTest(focusArea = "launcher")
     @Test
     public void testResizeWidget() throws Throwable {