Merge "Creating a correctly populated mOccupied grid when reordering on foldables" into udc-qpr-dev
diff --git a/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java b/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
index ecb8365..f99155f 100644
--- a/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
+++ b/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
@@ -188,12 +188,27 @@
                 return response;
             }
 
+            case TestProtocol.REQUEST_REINITIALIZE_DATA: {
+                final long identity = Binder.clearCallingIdentity();
+                try {
+                    MODEL_EXECUTOR.execute(() -> {
+                        LauncherModel model = LauncherAppState.getInstance(mContext).getModel();
+                        model.getModelDbController().createEmptyDB();
+                        MAIN_EXECUTOR.execute(model::forceReload);
+                    });
+                    return response;
+                } finally {
+                    Binder.restoreCallingIdentity(identity);
+                }
+            }
+
             case TestProtocol.REQUEST_CLEAR_DATA: {
                 final long identity = Binder.clearCallingIdentity();
                 try {
                     MODEL_EXECUTOR.execute(() -> {
                         LauncherModel model = LauncherAppState.getInstance(mContext).getModel();
                         model.getModelDbController().createEmptyDB();
+                        model.getModelDbController().clearEmptyDbFlag();
                         MAIN_EXECUTOR.execute(model::forceReload);
                     });
                     return response;
diff --git a/quickstep/res/layout-sw600dp-land/gesture_tutorial_step_menu.xml b/quickstep/res/layout-sw600dp-land/gesture_tutorial_step_menu.xml
index 225b4cd..672440f 100644
--- a/quickstep/res/layout-sw600dp-land/gesture_tutorial_step_menu.xml
+++ b/quickstep/res/layout-sw600dp-land/gesture_tutorial_step_menu.xml
@@ -13,155 +13,163 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<androidx.constraintlayout.widget.ConstraintLayout
+<FrameLayout
     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"
     android:theme="@style/GestureTutorialActivity"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:paddingTop="@dimen/gesture_tutorial_menu_padding_top"
-    android:paddingBottom="@dimen/gesture_tutorial_menu_padding_bottom"
-    android:paddingHorizontal="@dimen/gesture_tutorial_menu_padding_horizontal"
-    android:background="?androidprv:attr/materialColorSurfaceContainer">
+    android:background="?androidprv:attr/materialColorSurfaceContainer"
+    android:fitsSystemWindows="true">
 
     <androidx.constraintlayout.widget.ConstraintLayout
-        android:id="@+id/gesture_tutorial_menu_home_button"
-        android:layout_width="0dp"
-        android:layout_height="@dimen/gesture_tutorial_menu_button_height"
-        android:layout_marginEnd="@dimen/gesture_tutorial_menu_button_spacing"
-        android:layout_marginBottom="24dp"
-        android:background="@drawable/gesture_tutorial_menu_home_button_background"
-        android:clipToOutline="true"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:paddingTop="@dimen/gesture_tutorial_menu_padding_top"
+        android:paddingBottom="@dimen/gesture_tutorial_menu_padding_bottom"
+        android:paddingHorizontal="@dimen/gesture_tutorial_menu_padding_horizontal"
+        android:clipToPadding="false">
 
-        app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintBottom_toTopOf="@id/guideline"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toStartOf="@id/gesture_tutorial_menu_back_button">
-
-        <ImageView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:src="@drawable/gesture_tutorial_home_step_shape"
-            android:scaleType="fitXY"
-            android:adjustViewBounds="true"
-
-            app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintEnd_toEndOf="parent"/>
-
-        <TextView
-            style="@style/TextAppearance.GestureTutorial.MenuButton.Home"
-            android:id="@+id/gesture_tutorial_menu_home_button_text"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/home_gesture_tutorial_title"
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:id="@+id/gesture_tutorial_menu_home_button"
+            android:layout_width="0dp"
+            android:layout_height="@dimen/gesture_tutorial_menu_button_height"
+            android:layout_marginEnd="@dimen/gesture_tutorial_menu_button_spacing"
+            android:layout_marginBottom="24dp"
+            android:background="@drawable/gesture_tutorial_menu_home_button_background"
+            android:clipToOutline="true"
 
             app:layout_constraintTop_toTopOf="parent"
-            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintBottom_toTopOf="@id/guideline"
             app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintEnd_toStartOf="@id/gesture_tutorial_menu_back_button">
+
+            <ImageView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:src="@drawable/gesture_tutorial_home_step_shape"
+                android:scaleType="fitXY"
+                android:adjustViewBounds="true"
+
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintEnd_toEndOf="parent"/>
+
+            <TextView
+                style="@style/TextAppearance.GestureTutorial.MenuButton.Home"
+                android:id="@+id/gesture_tutorial_menu_home_button_text"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/home_gesture_tutorial_title"
+
+                app:layout_constraintTop_toTopOf="parent"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintEnd_toEndOf="parent"/>
+
+        </androidx.constraintlayout.widget.ConstraintLayout>
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:id="@+id/gesture_tutorial_menu_back_button"
+            android:layout_width="0dp"
+            android:layout_height="@dimen/gesture_tutorial_menu_button_height"
+            android:layout_marginEnd="@dimen/gesture_tutorial_menu_button_spacing"
+            android:layout_marginBottom="24dp"
+            android:background="@drawable/gesture_tutorial_menu_back_button_background"
+            android:clipToOutline="true"
+
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintBottom_toTopOf="@id/guideline"
+            app:layout_constraintStart_toEndOf="@id/gesture_tutorial_menu_home_button"
+            app:layout_constraintEnd_toStartOf="@id/gesture_tutorial_menu_overview_button">
+
+            <ImageView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:src="@drawable/gesture_tutorial_back_step_shape"
+                android:layout_marginBottom="@dimen/gesture_tutorial_menu_back_shape_bottom_margin"
+                android:scaleType="fitXY"
+                android:adjustViewBounds="true"
+
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintStart_toStartOf="parent"/>
+
+            <TextView
+                style="@style/TextAppearance.GestureTutorial.MenuButton.Back"
+                android:id="@+id/gesture_tutorial_menu_back_button_text"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/back_gesture_tutorial_title"
+
+                app:layout_constraintTop_toTopOf="parent"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintEnd_toEndOf="parent"/>
+
+        </androidx.constraintlayout.widget.ConstraintLayout>
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:id="@+id/gesture_tutorial_menu_overview_button"
+            android:layout_width="0dp"
+            android:layout_height="@dimen/gesture_tutorial_menu_button_height"
+            android:layout_marginBottom="24dp"
+            android:background="@drawable/gesture_tutorial_menu_overview_button_background"
+            android:clipToOutline="true"
+
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintBottom_toTopOf="@id/guideline"
+            app:layout_constraintStart_toEndOf="@id/gesture_tutorial_menu_back_button"
+            app:layout_constraintEnd_toEndOf="parent">
+
+            <ImageView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:src="@drawable/gesture_tutorial_overview_step_shape"
+                android:scaleType="fitXY"
+                android:adjustViewBounds="true"
+
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintEnd_toEndOf="parent"/>
+
+            <TextView
+                style="@style/TextAppearance.GestureTutorial.MenuButton.Overview"
+                android:id="@+id/gesture_tutorial_menu_overview_button_text"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/overview_gesture_tutorial_title"
+
+                app:layout_constraintTop_toTopOf="parent"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintEnd_toEndOf="parent"/>
+
+        </androidx.constraintlayout.widget.ConstraintLayout>
+
+        <androidx.constraintlayout.widget.Guideline
+            android:id="@+id/guideline"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+
+            app:layout_constraintGuide_end="@dimen/gesture_tutorial_menu_done_button_spacing"/>
+
+        <Button
+            style="@style/TextAppearance.GestureTutorial.ButtonLabel"
+            android:id="@+id/gesture_tutorial_menu_done_button"
+            android:layout_width="wrap_content"
+            android:layout_height="40dp"
+            android:layout_marginVertical="16dp"
+            android:text="@string/gesture_tutorial_action_button_label"
+            android:background="@drawable/gesture_tutorial_action_button_background"
+            android:backgroundTint="?androidprv:attr/materialColorPrimary"
+            android:stateListAnimator="@null"
+
+            app:layout_constraintTop_toBottomOf="@id/guideline"
+            app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintEnd_toEndOf="parent"/>
 
     </androidx.constraintlayout.widget.ConstraintLayout>
 
-    <androidx.constraintlayout.widget.ConstraintLayout
-        android:id="@+id/gesture_tutorial_menu_back_button"
-        android:layout_width="0dp"
-        android:layout_height="@dimen/gesture_tutorial_menu_button_height"
-        android:layout_marginEnd="@dimen/gesture_tutorial_menu_button_spacing"
-        android:layout_marginBottom="24dp"
-        android:background="@drawable/gesture_tutorial_menu_back_button_background"
-        android:clipToOutline="true"
-
-        app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintBottom_toTopOf="@id/guideline"
-        app:layout_constraintStart_toEndOf="@id/gesture_tutorial_menu_home_button"
-        app:layout_constraintEnd_toStartOf="@id/gesture_tutorial_menu_overview_button">
-
-        <ImageView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:src="@drawable/gesture_tutorial_back_step_shape"
-            android:layout_marginBottom="@dimen/gesture_tutorial_menu_back_shape_bottom_margin"
-            android:scaleType="fitXY"
-            android:adjustViewBounds="true"
-
-            app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintStart_toStartOf="parent"/>
-
-        <TextView
-            style="@style/TextAppearance.GestureTutorial.MenuButton.Back"
-            android:id="@+id/gesture_tutorial_menu_back_button_text"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/back_gesture_tutorial_title"
-
-            app:layout_constraintTop_toTopOf="parent"
-            app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintEnd_toEndOf="parent"/>
-
-    </androidx.constraintlayout.widget.ConstraintLayout>
-
-    <androidx.constraintlayout.widget.ConstraintLayout
-        android:id="@+id/gesture_tutorial_menu_overview_button"
-        android:layout_width="0dp"
-        android:layout_height="@dimen/gesture_tutorial_menu_button_height"
-        android:layout_marginBottom="24dp"
-        android:background="@drawable/gesture_tutorial_menu_overview_button_background"
-        android:clipToOutline="true"
-
-        app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintBottom_toTopOf="@id/guideline"
-        app:layout_constraintStart_toEndOf="@id/gesture_tutorial_menu_back_button"
-        app:layout_constraintEnd_toEndOf="parent">
-
-        <ImageView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:src="@drawable/gesture_tutorial_overview_step_shape"
-            android:scaleType="fitXY"
-            android:adjustViewBounds="true"
-
-            app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintEnd_toEndOf="parent"/>
-
-        <TextView
-            style="@style/TextAppearance.GestureTutorial.MenuButton.Overview"
-            android:id="@+id/gesture_tutorial_menu_overview_button_text"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/overview_gesture_tutorial_title"
-
-            app:layout_constraintTop_toTopOf="parent"
-            app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintEnd_toEndOf="parent"/>
-
-    </androidx.constraintlayout.widget.ConstraintLayout>
-
-    <androidx.constraintlayout.widget.Guideline
-        android:id="@+id/guideline"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:orientation="horizontal"
-
-        app:layout_constraintGuide_end="@dimen/gesture_tutorial_menu_done_button_spacing"/>
-
-    <Button
-        style="@style/TextAppearance.GestureTutorial.ButtonLabel"
-        android:id="@+id/gesture_tutorial_menu_done_button"
-        android:layout_width="wrap_content"
-        android:layout_height="40dp"
-        android:layout_marginVertical="16dp"
-        android:text="@string/gesture_tutorial_action_button_label"
-        android:background="@drawable/gesture_tutorial_action_button_background"
-        android:backgroundTint="?androidprv:attr/materialColorPrimary"
-        android:stateListAnimator="@null"
-
-        app:layout_constraintTop_toBottomOf="@id/guideline"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"/>
-
-</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
+</FrameLayout>
\ No newline at end of file
diff --git a/quickstep/res/layout/activity_allset.xml b/quickstep/res/layout/activity_allset.xml
index 2c312a7..685a151 100644
--- a/quickstep/res/layout/activity_allset.xml
+++ b/quickstep/res/layout/activity_allset.xml
@@ -41,6 +41,7 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:fillViewport="true"
+        android:fitsSystemWindows="true"
 
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintBottom_toBottomOf="parent"
@@ -95,7 +96,6 @@
                 style="@style/TextAppearance.GestureTutorial.Feedback.Subtitle"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:layout_marginBottom="24dp"
                 android:text="@string/allset_hint"
                 android:textSize="@dimen/allset_page_swipe_up_text_size"
                 android:gravity="center_horizontal"
diff --git a/quickstep/res/layout/gesture_tutorial_step_menu.xml b/quickstep/res/layout/gesture_tutorial_step_menu.xml
index cf78b1b..c8ee6e9 100644
--- a/quickstep/res/layout/gesture_tutorial_step_menu.xml
+++ b/quickstep/res/layout/gesture_tutorial_step_menu.xml
@@ -13,154 +13,161 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<androidx.constraintlayout.widget.ConstraintLayout
+<FrameLayout
     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"
     android:theme="@style/GestureTutorialActivity"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:paddingTop="@dimen/gesture_tutorial_menu_padding_top"
-    android:paddingBottom="@dimen/gesture_tutorial_menu_padding_bottom"
-    android:paddingHorizontal="@dimen/gesture_tutorial_menu_padding_horizontal"
     android:background="?androidprv:attr/materialColorSurfaceContainer"
-    android:clipToPadding="false">
+    android:fitsSystemWindows="true">
 
     <androidx.constraintlayout.widget.ConstraintLayout
-        android:id="@+id/gesture_tutorial_menu_home_button"
         android:layout_width="match_parent"
-        android:layout_height="@dimen/gesture_tutorial_menu_button_height"
-        android:background="@drawable/gesture_tutorial_menu_home_button_background"
-        android:clipToOutline="true"
-        app:layout_constraintVertical_chainStyle="packed"
+        android:layout_height="match_parent"
+        android:paddingTop="@dimen/gesture_tutorial_menu_padding_top"
+        android:paddingBottom="@dimen/gesture_tutorial_menu_padding_bottom"
+        android:paddingHorizontal="@dimen/gesture_tutorial_menu_padding_horizontal"
+        android:clipToPadding="false">
 
-        app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintBottom_toTopOf="@id/gesture_tutorial_menu_back_button"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toEndOf="parent">
-
-        <ImageView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:src="@drawable/gesture_tutorial_home_step_shape"
-            android:scaleType="fitXY"
-            android:adjustViewBounds="true"
-
-            app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintEnd_toEndOf="parent"/>
-
-        <TextView
-            style="@style/TextAppearance.GestureTutorial.MenuButton.Home"
-            android:id="@+id/gesture_tutorial_menu_home_button_text"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/home_gesture_tutorial_title"
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:id="@+id/gesture_tutorial_menu_home_button"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/gesture_tutorial_menu_button_height"
+            android:background="@drawable/gesture_tutorial_menu_home_button_background"
+            android:clipToOutline="true"
+            app:layout_constraintVertical_chainStyle="packed"
 
             app:layout_constraintTop_toTopOf="parent"
-            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintBottom_toTopOf="@id/gesture_tutorial_menu_back_button"
             app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintEnd_toEndOf="parent">
+
+            <ImageView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:src="@drawable/gesture_tutorial_home_step_shape"
+                android:scaleType="fitXY"
+                android:adjustViewBounds="true"
+
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintEnd_toEndOf="parent"/>
+
+            <TextView
+                style="@style/TextAppearance.GestureTutorial.MenuButton.Home"
+                android:id="@+id/gesture_tutorial_menu_home_button_text"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/home_gesture_tutorial_title"
+
+                app:layout_constraintTop_toTopOf="parent"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintEnd_toEndOf="parent"/>
+
+        </androidx.constraintlayout.widget.ConstraintLayout>
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:id="@+id/gesture_tutorial_menu_back_button"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/gesture_tutorial_menu_button_height"
+            android:layout_marginTop="@dimen/gesture_tutorial_menu_button_spacing"
+            android:background="@drawable/gesture_tutorial_menu_back_button_background"
+            android:clipToOutline="true"
+
+            app:layout_constraintTop_toBottomOf="@id/gesture_tutorial_menu_home_button"
+            app:layout_constraintBottom_toTopOf="@id/gesture_tutorial_menu_overview_button"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintEnd_toEndOf="parent">
+
+            <ImageView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:src="@drawable/gesture_tutorial_back_step_shape"
+                android:scaleType="fitXY"
+                android:adjustViewBounds="true"
+
+                app:layout_constraintTop_toTopOf="parent"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintStart_toStartOf="parent"/>
+
+            <TextView
+                style="@style/TextAppearance.GestureTutorial.MenuButton.Back"
+                android:id="@+id/gesture_tutorial_menu_back_button_text"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/back_gesture_tutorial_title"
+
+                app:layout_constraintTop_toTopOf="parent"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintEnd_toEndOf="parent"/>
+
+        </androidx.constraintlayout.widget.ConstraintLayout>
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:id="@+id/gesture_tutorial_menu_overview_button"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/gesture_tutorial_menu_button_height"
+            android:layout_marginTop="@dimen/gesture_tutorial_menu_button_spacing"
+            android:background="@drawable/gesture_tutorial_menu_overview_button_background"
+            android:clipToOutline="true"
+
+            app:layout_constraintTop_toBottomOf="@id/gesture_tutorial_menu_back_button"
+            app:layout_constraintBottom_toTopOf="@id/guideline"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintEnd_toEndOf="parent">
+
+            <ImageView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:src="@drawable/gesture_tutorial_overview_step_shape"
+                android:scaleType="fitXY"
+                android:adjustViewBounds="true"
+
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintEnd_toEndOf="parent"/>
+
+            <TextView
+                style="@style/TextAppearance.GestureTutorial.MenuButton.Overview"
+                android:id="@+id/gesture_tutorial_menu_overview_button_text"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/overview_gesture_tutorial_title"
+
+                app:layout_constraintTop_toTopOf="parent"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintEnd_toEndOf="parent"/>
+
+        </androidx.constraintlayout.widget.ConstraintLayout>
+
+        <androidx.constraintlayout.widget.Guideline
+            android:id="@+id/guideline"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+
+            app:layout_constraintGuide_end="@dimen/gesture_tutorial_menu_done_button_spacing"/>
+
+        <Button
+            style="@style/TextAppearance.GestureTutorial.ButtonLabel"
+            android:id="@+id/gesture_tutorial_menu_done_button"
+            android:layout_width="wrap_content"
+            android:layout_height="40dp"
+            android:layout_marginVertical="16dp"
+            android:text="@string/gesture_tutorial_action_button_label"
+            android:background="@drawable/gesture_tutorial_action_button_background"
+            android:backgroundTint="?androidprv:attr/materialColorPrimary"
+            android:stateListAnimator="@null"
+
+            app:layout_constraintTop_toBottomOf="@id/guideline"
+            app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintEnd_toEndOf="parent"/>
 
     </androidx.constraintlayout.widget.ConstraintLayout>
 
-    <androidx.constraintlayout.widget.ConstraintLayout
-        android:id="@+id/gesture_tutorial_menu_back_button"
-        android:layout_width="match_parent"
-        android:layout_height="@dimen/gesture_tutorial_menu_button_height"
-        android:layout_marginTop="@dimen/gesture_tutorial_menu_button_spacing"
-        android:background="@drawable/gesture_tutorial_menu_back_button_background"
-        android:clipToOutline="true"
-
-        app:layout_constraintTop_toBottomOf="@id/gesture_tutorial_menu_home_button"
-        app:layout_constraintBottom_toTopOf="@id/gesture_tutorial_menu_overview_button"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toEndOf="parent">
-
-        <ImageView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:src="@drawable/gesture_tutorial_back_step_shape"
-            android:scaleType="fitXY"
-            android:adjustViewBounds="true"
-
-            app:layout_constraintTop_toTopOf="parent"
-            app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintStart_toStartOf="parent"/>
-
-        <TextView
-            style="@style/TextAppearance.GestureTutorial.MenuButton.Back"
-            android:id="@+id/gesture_tutorial_menu_back_button_text"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/back_gesture_tutorial_title"
-
-            app:layout_constraintTop_toTopOf="parent"
-            app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintEnd_toEndOf="parent"/>
-
-    </androidx.constraintlayout.widget.ConstraintLayout>
-
-    <androidx.constraintlayout.widget.ConstraintLayout
-        android:id="@+id/gesture_tutorial_menu_overview_button"
-        android:layout_width="match_parent"
-        android:layout_height="@dimen/gesture_tutorial_menu_button_height"
-        android:layout_marginTop="@dimen/gesture_tutorial_menu_button_spacing"
-        android:background="@drawable/gesture_tutorial_menu_overview_button_background"
-        android:clipToOutline="true"
-
-        app:layout_constraintTop_toBottomOf="@id/gesture_tutorial_menu_back_button"
-        app:layout_constraintBottom_toTopOf="@id/guideline"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toEndOf="parent">
-
-        <ImageView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:src="@drawable/gesture_tutorial_overview_step_shape"
-            android:scaleType="fitXY"
-            android:adjustViewBounds="true"
-
-            app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintEnd_toEndOf="parent"/>
-
-        <TextView
-            style="@style/TextAppearance.GestureTutorial.MenuButton.Overview"
-            android:id="@+id/gesture_tutorial_menu_overview_button_text"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/overview_gesture_tutorial_title"
-
-            app:layout_constraintTop_toTopOf="parent"
-            app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintEnd_toEndOf="parent"/>
-
-    </androidx.constraintlayout.widget.ConstraintLayout>
-
-    <androidx.constraintlayout.widget.Guideline
-        android:id="@+id/guideline"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:orientation="horizontal"
-
-        app:layout_constraintGuide_end="@dimen/gesture_tutorial_menu_done_button_spacing"/>
-
-    <Button
-        style="@style/TextAppearance.GestureTutorial.ButtonLabel"
-        android:id="@+id/gesture_tutorial_menu_done_button"
-        android:layout_width="wrap_content"
-        android:layout_height="40dp"
-        android:layout_marginVertical="16dp"
-        android:text="@string/gesture_tutorial_action_button_label"
-        android:background="@drawable/gesture_tutorial_action_button_background"
-        android:backgroundTint="?androidprv:attr/materialColorPrimary"
-        android:stateListAnimator="@null"
-
-        app:layout_constraintTop_toBottomOf="@id/guideline"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"/>
-
-</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
+</FrameLayout>
\ No newline at end of file
diff --git a/quickstep/res/layout/transient_taskbar.xml b/quickstep/res/layout/transient_taskbar.xml
index bf4b811..7a6d16a 100644
--- a/quickstep/res/layout/transient_taskbar.xml
+++ b/quickstep/res/layout/transient_taskbar.xml
@@ -44,7 +44,6 @@
         android:layout_height="@dimen/bubblebar_size"
         android:layout_gravity="bottom|end"
         android:layout_marginEnd="@dimen/transient_taskbar_bottom_margin"
-        android:layout_marginBottom="@dimen/transient_taskbar_bottom_margin"
         android:paddingEnd="@dimen/taskbar_icon_spacing"
         android:paddingStart="@dimen/taskbar_icon_spacing"
         android:visibility="gone"
diff --git a/quickstep/res/values-bg/strings.xml b/quickstep/res/values-bg/strings.xml
index 506d5a7..c8f19f1 100644
--- a/quickstep/res/values-bg/strings.xml
+++ b/quickstep/res/values-bg/strings.xml
@@ -86,7 +86,7 @@
     <string name="gesture_tutorial_nice" msgid="2936275692616928280">"Чудесно!"</string>
     <string name="gesture_tutorial_step" msgid="1279786122817620968">"Урок <xliff:g id="CURRENT">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
     <string name="allset_title" msgid="5021126669778966707">"Готово!"</string>
-    <string name="allset_hint" msgid="459504134589971527">"Прекарайте пръст нагоре, за да отворите началния екран"</string>
+    <string name="allset_hint" msgid="459504134589971527">"Плъзнете пръст нагоре, за да отворите началния екран"</string>
     <string name="allset_button_hint" msgid="2395219947744706291">"Докоснете бутона „Начало“, за да преминете към началния екран"</string>
     <string name="allset_description_generic" msgid="5385500062202019855">"Можете да започнете да използвате <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="default_device_name" msgid="6660656727127422487">"устройството"</string>
diff --git a/quickstep/res/values-bs/strings.xml b/quickstep/res/values-bs/strings.xml
index 193476e..0497fe4 100644
--- a/quickstep/res/values-bs/strings.xml
+++ b/quickstep/res/values-bs/strings.xml
@@ -88,7 +88,7 @@
     <string name="allset_title" msgid="5021126669778966707">"Sve je spremno!"</string>
     <string name="allset_hint" msgid="459504134589971527">"Prevucite prema gore da odete na početni ekran"</string>
     <string name="allset_button_hint" msgid="2395219947744706291">"Dodirnite dugme za početni ekran da odete napočetni ekran"</string>
-    <string name="allset_description_generic" msgid="5385500062202019855">"Sve je spremno da počnete koristiti <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
+    <string name="allset_description_generic" msgid="5385500062202019855">"Spremni ste da počnete koristiti <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="default_device_name" msgid="6660656727127422487">"uređaj"</string>
     <string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Postavke navigiranja sistemom"</annotation></string>
     <string name="action_share" msgid="2648470652637092375">"Dijeli"</string>
diff --git a/quickstep/res/values-da/strings.xml b/quickstep/res/values-da/strings.xml
index 4766523..bfce9a1 100644
--- a/quickstep/res/values-da/strings.xml
+++ b/quickstep/res/values-da/strings.xml
@@ -84,7 +84,7 @@
     <string name="gesture_tutorial_action_button_label_settings" msgid="2923621047916486604">"Indstillinger"</string>
     <string name="gesture_tutorial_try_again" msgid="65962545858556697">"Prøv igen"</string>
     <string name="gesture_tutorial_nice" msgid="2936275692616928280">"Sådan!"</string>
-    <string name="gesture_tutorial_step" msgid="1279786122817620968">"Selvstudie <xliff:g id="CURRENT">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
+    <string name="gesture_tutorial_step" msgid="1279786122817620968">"Vejledning <xliff:g id="CURRENT">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
     <string name="allset_title" msgid="5021126669778966707">"Alt er parat!"</string>
     <string name="allset_hint" msgid="459504134589971527">"Stryg opad for at gå til startsiden"</string>
     <string name="allset_button_hint" msgid="2395219947744706291">"Tryk på knappen Hjem for at gå til din startskærm"</string>
@@ -97,7 +97,7 @@
     <string name="toast_split_select_app" msgid="8464310533320556058">"Tryk på en anden app for at bruge opdelt skærm"</string>
     <string name="toast_split_app_unsupported" msgid="2360229567007828914">"Vælg en anden app for at bruge opdelt skærm"</string>
     <string name="blocked_by_policy" msgid="2071401072261365546">"Appen eller din organisation tillader ikke denne handling"</string>
-    <string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Vil du springe selvstudiet for navigation over?"</string>
+    <string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Vil du springe vejledningen for navigation over?"</string>
     <string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Du kan finde dette senere i appen <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Annuller"</string>
     <string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Spring over"</string>
diff --git a/quickstep/res/values-es-rUS/strings.xml b/quickstep/res/values-es-rUS/strings.xml
index 98d662b..35ab3db 100644
--- a/quickstep/res/values-es-rUS/strings.xml
+++ b/quickstep/res/values-es-rUS/strings.xml
@@ -23,7 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Formato libre"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"No hay elementos recientes"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Configuración de uso de la app"</string>
-    <string name="recents_clear_all" msgid="5328176793634888831">"Borrar todo"</string>
+    <string name="recents_clear_all" msgid="5328176793634888831">"Cerrar todo"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Apps recientes"</string>
     <string name="task_view_closed" msgid="9170038230110856166">"Se cerró la tarea"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g> (<xliff:g id="REMAINING_TIME">%2$s</xliff:g>)"</string>
diff --git a/quickstep/res/values-eu/strings.xml b/quickstep/res/values-eu/strings.xml
index 9293207..b876cf4 100644
--- a/quickstep/res/values-eu/strings.xml
+++ b/quickstep/res/values-eu/strings.xml
@@ -58,7 +58,7 @@
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Egin atzera"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Pasatu hatza pantailaren eskuineko edo ezkerreko ertzetik erdialdera"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Ziurtatu hatza pantailaren beheko ertzetik gora pasatzen duzula"</string>
-    <string name="home_gesture_feedback_overview_detected" msgid="5177627157303895077">"Ziurtatu hatza jaso aurretik ez zarela gelditzen"</string>
+    <string name="home_gesture_feedback_overview_detected" msgid="5177627157303895077">"Ziurtatu ez duzula mugimendua gelditzen askatu arte"</string>
     <string name="home_gesture_feedback_wrong_swipe_direction" msgid="8328465201424027148">"Ziurtatu hatza zuzen pasatzen duzula gora"</string>
     <string name="home_gesture_feedback_complete_with_follow_up" msgid="8766981412895888417">"Ikasi duzu hasierako pantailara joateko keinua. Jarraian, ikasi atzera egiten."</string>
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"Ikasi duzu hasierako pantailara joateko keinua"</string>
diff --git a/quickstep/res/values-hi/strings.xml b/quickstep/res/values-hi/strings.xml
index 2b58605..6affb00 100644
--- a/quickstep/res/values-hi/strings.xml
+++ b/quickstep/res/values-hi/strings.xml
@@ -88,7 +88,7 @@
     <string name="allset_title" msgid="5021126669778966707">"हो गया!"</string>
     <string name="allset_hint" msgid="459504134589971527">"होम पेज पर जाने के लिए, ऊपर की ओर स्वाइप करें"</string>
     <string name="allset_button_hint" msgid="2395219947744706291">"होम स्क्रीन पर जाने के लिए, होम बटन पर टैप करें"</string>
-    <string name="allset_description_generic" msgid="5385500062202019855">"अब <xliff:g id="DEVICE">%1$s</xliff:g> इस्तेमाल के लिए तैयार हैं"</string>
+    <string name="allset_description_generic" msgid="5385500062202019855">"अब <xliff:g id="DEVICE">%1$s</xliff:g> इस्तेमाल के लिए तैयार है"</string>
     <string name="default_device_name" msgid="6660656727127422487">"डिवाइस"</string>
     <string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"सिस्टम नेविगेशन सेटिंग"</annotation></string>
     <string name="action_share" msgid="2648470652637092375">"शेयर करें"</string>
diff --git a/quickstep/res/values-kn/strings.xml b/quickstep/res/values-kn/strings.xml
index c155b38..0da707a 100644
--- a/quickstep/res/values-kn/strings.xml
+++ b/quickstep/res/values-kn/strings.xml
@@ -87,7 +87,7 @@
     <string name="gesture_tutorial_step" msgid="1279786122817620968">"ಟ್ಯುಟೋರಿಯಲ್ <xliff:g id="CURRENT">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
     <string name="allset_title" msgid="5021126669778966707">"ಎಲ್ಲವೂ ಸಿದ್ಧವಾಗಿದೆ!"</string>
     <string name="allset_hint" msgid="459504134589971527">"ಮುಖಪುಟಕ್ಕೆ ಹೋಗಲು ಮೇಲೆ ಸ್ವೈಪ್ ಮಾಡಿ"</string>
-    <string name="allset_button_hint" msgid="2395219947744706291">"ನಿಮ್ಮ ಮುಖಪುಟದ ಪರದೆಗೆ ಹೋಗಲು ಮುಖಪುಟ ಬಟನ್ ಅನ್ನು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
+    <string name="allset_button_hint" msgid="2395219947744706291">"ನಿಮ್ಮ ಹೋಮ್ ಸ್ಕ್ರೀನ್‌ಗೆ ಹೋಗಲು ಹೋಮ್ ಬಟನ್ ಅನ್ನು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
     <string name="allset_description_generic" msgid="5385500062202019855">"ನಿಮ್ಮ <xliff:g id="DEVICE">%1$s</xliff:g> ಬಳಸುವುದನ್ನು ಪ್ರಾರಂಭಿಸಲು ನೀವು ಸಿದ್ಧರಾಗಿರುವಿರಿ"</string>
     <string name="default_device_name" msgid="6660656727127422487">"ಸಾಧನ"</string>
     <string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"ಸಿಸ್ಟಂ ನ್ಯಾವಿಗೇಶನ್ ಸೆಟ್ಟಿಂಗ್‌ಗಳು"</annotation></string>
diff --git a/quickstep/res/values-ms/strings.xml b/quickstep/res/values-ms/strings.xml
index a62a24c..9ae478b 100644
--- a/quickstep/res/values-ms/strings.xml
+++ b/quickstep/res/values-ms/strings.xml
@@ -86,7 +86,7 @@
     <string name="gesture_tutorial_nice" msgid="2936275692616928280">"Bagus!"</string>
     <string name="gesture_tutorial_step" msgid="1279786122817620968">"Tutorial <xliff:g id="CURRENT">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
     <string name="allset_title" msgid="5021126669778966707">"Siap!"</string>
-    <string name="allset_hint" msgid="459504134589971527">"Leret ke atas untuk mencapai laman utama"</string>
+    <string name="allset_hint" msgid="459504134589971527">"Leret ke atas untuk ke laman utama"</string>
     <string name="allset_button_hint" msgid="2395219947744706291">"Ketik butang skrin utama untuk pergi ke skrin utama anda"</string>
     <string name="allset_description_generic" msgid="5385500062202019855">"Anda sudah sedia untuk mula menggunakan <xliff:g id="DEVICE">%1$s</xliff:g> anda"</string>
     <string name="default_device_name" msgid="6660656727127422487">"peranti"</string>
diff --git a/quickstep/res/values-or/strings.xml b/quickstep/res/values-or/strings.xml
index 203b149..52777c2 100644
--- a/quickstep/res/values-or/strings.xml
+++ b/quickstep/res/values-or/strings.xml
@@ -86,7 +86,7 @@
     <string name="gesture_tutorial_nice" msgid="2936275692616928280">"ବଢ଼ିଆ!"</string>
     <string name="gesture_tutorial_step" msgid="1279786122817620968">"ଟ୍ୟୁଟୋରିଆଲ୍ <xliff:g id="CURRENT">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
     <string name="allset_title" msgid="5021126669778966707">"ସମ୍ପୂର୍ଣ୍ଣ ଭାବେ ପ୍ରସ୍ତୁତ!"</string>
-    <string name="allset_hint" msgid="459504134589971527">"ମୂଳପୃଷ୍ଠାକୁ ଯିବା ପାଇଁ ଉପରକୁ ସ୍ୱାଇପ୍ କରନ୍ତୁ"</string>
+    <string name="allset_hint" msgid="459504134589971527">"ହୋମକୁ ଯିବା ପାଇଁ ଉପରକୁ ସ୍ୱାଇପ କରନ୍ତୁ"</string>
     <string name="allset_button_hint" msgid="2395219947744706291">"ଆପଣଙ୍କ ହୋମ ସ୍କ୍ରିନକୁ ଯିବା ପାଇଁ ହୋମ ବଟନରେ ଟାପ କରନ୍ତୁ"</string>
     <string name="allset_description_generic" msgid="5385500062202019855">"ଆପଣ ଆପଣଙ୍କ <xliff:g id="DEVICE">%1$s</xliff:g> ବ୍ୟବହାର କରିବା ଆରମ୍ଭ କରିବାକୁ ପ୍ରସ୍ତୁତ ଅଛନ୍ତି"</string>
     <string name="default_device_name" msgid="6660656727127422487">"ଡିଭାଇସ"</string>
diff --git a/quickstep/res/values-pl/strings.xml b/quickstep/res/values-pl/strings.xml
index 40aba40..5f609b2 100644
--- a/quickstep/res/values-pl/strings.xml
+++ b/quickstep/res/values-pl/strings.xml
@@ -55,7 +55,7 @@
     <string name="back_gesture_intro_title" msgid="19551256430224428">"Przesuń palcem, aby przejść wstecz"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"Aby wrócić do poprzedniego ekranu, przesuń palcem od lewej lub prawej krawędzi do środka ekranu."</string>
     <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"Aby wrócić do poprzedniego ekranu, przesuń 2 palcami od lewej lub prawej krawędzi do środka ekranu."</string>
-    <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Przejdź wstecz"</string>
+    <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Przejście wstecz"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Przesuń palcem od lewej lub prawej krawędzi do środka ekranu"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Pamiętaj, aby przesuwać palcem od dolnej krawędzi ekranu"</string>
     <string name="home_gesture_feedback_overview_detected" msgid="5177627157303895077">"Pamiętaj, aby przed podniesieniem palca nie było przerwy"</string>
diff --git a/quickstep/res/values-te/strings.xml b/quickstep/res/values-te/strings.xml
index f80153c..ddebce9 100644
--- a/quickstep/res/values-te/strings.xml
+++ b/quickstep/res/values-te/strings.xml
@@ -86,7 +86,7 @@
     <string name="gesture_tutorial_nice" msgid="2936275692616928280">"పనితీరు బాగుంది!"</string>
     <string name="gesture_tutorial_step" msgid="1279786122817620968">"ట్యుటోరియల్ <xliff:g id="CURRENT">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
     <string name="allset_title" msgid="5021126669778966707">"అంతా సెట్ అయింది!"</string>
-    <string name="allset_hint" msgid="459504134589971527">"హోమ్‌కు వెళ్లడానికి పైకి స్వైప్ చేయండి"</string>
+    <string name="allset_hint" msgid="459504134589971527">"వర్చువల్ హోమ్‌కు వెళ్లడానికి పైకి స్వైప్ చేయండి"</string>
     <string name="allset_button_hint" msgid="2395219947744706291">"మీ మొదటి స్క్రీన్‌కు వెళ్లడానికి హోమ్ బటన్‌ను ట్యాప్ చేయండి"</string>
     <string name="allset_description_generic" msgid="5385500062202019855">"మీరు ఇప్పుడు మీ <xliff:g id="DEVICE">%1$s</xliff:g>‌ను ఉపయోగించడం ప్రారంభించవచ్చు"</string>
     <string name="default_device_name" msgid="6660656727127422487">"పరికరం"</string>
diff --git a/quickstep/res/values-zh-rCN/strings.xml b/quickstep/res/values-zh-rCN/strings.xml
index b5798ba..d51f46a 100644
--- a/quickstep/res/values-zh-rCN/strings.xml
+++ b/quickstep/res/values-zh-rCN/strings.xml
@@ -86,7 +86,7 @@
     <string name="gesture_tutorial_nice" msgid="2936275692616928280">"很好!"</string>
     <string name="gesture_tutorial_step" msgid="1279786122817620968">"教程 <xliff:g id="CURRENT">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
     <string name="allset_title" msgid="5021126669778966707">"大功告成!"</string>
-    <string name="allset_hint" msgid="459504134589971527">"向上滑动可转到主屏幕"</string>
+    <string name="allset_hint" msgid="459504134589971527">"向上滑动可前往主屏幕"</string>
     <string name="allset_button_hint" msgid="2395219947744706291">"点按主屏幕按钮即可前往主屏幕"</string>
     <string name="allset_description_generic" msgid="5385500062202019855">"您可以开始使用<xliff:g id="DEVICE">%1$s</xliff:g>了"</string>
     <string name="default_device_name" msgid="6660656727127422487">"设备"</string>
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 2b578c3..153c1ac 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -18,6 +18,7 @@
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.provider.Settings.Secure.LAUNCHER_TASKBAR_EDUCATION_SHOWING;
 import static android.view.RemoteAnimationTarget.MODE_CLOSING;
 import static android.view.RemoteAnimationTarget.MODE_OPENING;
@@ -1210,14 +1211,15 @@
         return false;
     }
 
-    private boolean hasMultipleTargetsWithMode(RemoteAnimationTarget[] targets, int mode) {
+    private boolean shouldPlayFallbackClosingAnimation(RemoteAnimationTarget[] targets) {
         int numTargets = 0;
         for (RemoteAnimationTarget target : targets) {
-            if (target.mode == mode) {
+            if (target.mode == MODE_CLOSING) {
                 numTargets++;
-            }
-            if (numTargets > 1) {
-                return true;
+                if (numTargets > 1 || target.windowConfiguration.getWindowingMode()
+                        == WINDOWING_MODE_MULTI_WINDOW) {
+                    return true;
+                }
             }
         }
         return false;
@@ -1604,7 +1606,7 @@
             boolean playFallBackAnimation = (launcherView == null
                     && launcherIsForceInvisibleOrOpening)
                     || mLauncher.getWorkspace().isOverlayShown()
-                    || hasMultipleTargetsWithMode(appTargets, MODE_CLOSING);
+                    || shouldPlayFallbackClosingAnimation(appTargets);
 
             boolean playWorkspaceReveal = true;
             boolean skipAllAppsScale = false;
diff --git a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
index 34316db..4f889c0 100644
--- a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
+++ b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.appprediction;
 
+import static com.android.launcher3.BubbleTextView.DISPLAY_PREDICTION_ROW;
+
 import android.annotation.TargetApi;
 import android.content.Context;
 import android.graphics.Canvas;
@@ -36,13 +38,11 @@
 import com.android.launcher3.allapps.FloatingHeaderRow;
 import com.android.launcher3.allapps.FloatingHeaderView;
 import com.android.launcher3.anim.AlphaUpdateListener;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.keyboard.FocusIndicatorHelper;
 import com.android.launcher3.keyboard.FocusIndicatorHelper.SimpleFocusIndicatorHelper;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.ItemInfoWithIcon;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.touch.ItemLongClickListener;
 import com.android.launcher3.views.ActivityContext;
 
 import java.util.ArrayList;
@@ -65,7 +65,6 @@
     private FloatingHeaderView mParent;
 
     private boolean mPredictionsEnabled = false;
-    private OnLongClickListener mOnIconLongClickListener = ItemLongClickListener.INSTANCE_ALL_APPS;
 
     public PredictionRowView(@NonNull Context context) {
         this(context, null);
@@ -106,22 +105,12 @@
                 mActivityContext.getAppsView().getAppsStore().unregisterIconContainer(this);
             }
         }
-
-        // Set the predicted row in All Apps' text line to 1.
-        if (FeatureFlags.ENABLE_TWOLINE_ALLAPPS.get()
-                || FeatureFlags.ENABLE_TWOLINE_DEVICESEARCH.get()) {
-            for (int i = 0; i < getChildCount(); i++) {
-                BubbleTextView icon = (BubbleTextView) getChildAt(i);
-                icon.setMaxLines(1);
-            }
-        }
     }
 
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(getExpectedHeight(),
                 MeasureSpec.EXACTLY));
-        updateVisibility();
     }
 
     @Override
@@ -185,15 +174,6 @@
         applyPredictionApps();
     }
 
-    /**
-     * Sets the long click listener for predictions for any future predictions.
-     *
-     * Existing predictions in the container are not updated with this new callback.
-     */
-    public void setOnIconLongClickListener(OnLongClickListener onIconLongClickListener) {
-        mOnIconLongClickListener = onIconLongClickListener;
-    }
-
     @Override
     public void onDeviceProfileChanged(DeviceProfile dp) {
         mNumPredictedAppsPerRow = dp.numShownAllAppsColumns;
@@ -211,7 +191,7 @@
                 BubbleTextView icon = (BubbleTextView) inflater.inflate(
                         R.layout.all_apps_icon, this, false);
                 icon.setOnClickListener(mActivityContext.getItemOnClickListener());
-                icon.setOnLongClickListener(mOnIconLongClickListener);
+                icon.setOnLongClickListener(mActivityContext.getAllAppsItemLongClickListener());
                 icon.setLongPressTimeoutFactor(1f);
                 icon.setOnFocusChangeListener(mFocusHelper);
 
@@ -231,6 +211,7 @@
             icon.reset();
             if (predictionCount > i) {
                 icon.setVisibility(View.VISIBLE);
+                icon.setDisplay(DISPLAY_PREDICTION_ROW);
                 icon.applyFromWorkspaceItem(mPredictedApps.get(i));
             } else {
                 icon.setVisibility(predictionCount == 0 ? GONE : INVISIBLE);
diff --git a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
index bc97df1..32361a8 100644
--- a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
+++ b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
@@ -52,6 +52,7 @@
 import androidx.annotation.CallSuper;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 import androidx.annotation.WorkerThread;
 
 import com.android.launcher3.InvariantDeviceProfile;
@@ -93,11 +94,14 @@
     private static final boolean IS_DEBUG = false;
     private static final String TAG = "QuickstepModelDelegate";
 
-    private final PredictorState mAllAppsState =
+    @VisibleForTesting
+    final PredictorState mAllAppsState =
             new PredictorState(CONTAINER_PREDICTION, "all_apps_predictions");
-    private final PredictorState mHotseatState =
+    @VisibleForTesting
+    final PredictorState mHotseatState =
             new PredictorState(CONTAINER_HOTSEAT_PREDICTION, "hotseat_predictions");
-    private final PredictorState mWidgetsRecommendationState =
+    @VisibleForTesting
+    final PredictorState mWidgetsRecommendationState =
             new PredictorState(CONTAINER_WIDGETS_PREDICTION, "widgets_prediction");
 
     private final InvariantDeviceProfile mIDP;
@@ -348,12 +352,7 @@
                         .build()));
 
         // TODO: get bundle
-        registerPredictor(mHotseatState, apm.createAppPredictionSession(
-                new AppPredictionContext.Builder(context)
-                        .setUiSurface("hotseat")
-                        .setPredictedTargetCount(mIDP.numDatabaseHotseatIcons)
-                        .setExtras(convertDataModelToAppTargetBundle(context, mDataModel))
-                        .build()));
+        registerHotseatPredictor(apm, context);
 
         registerWidgetsPredictor(apm.createAppPredictionSession(
                 new AppPredictionContext.Builder(context)
@@ -363,6 +362,29 @@
                         .build()));
     }
 
+    @WorkerThread
+    private void recreateHotseatPredictor() {
+        mHotseatState.destroyPredictor();
+        if (!mActive) {
+            return;
+        }
+        Context context = mApp.getContext();
+        AppPredictionManager apm = context.getSystemService(AppPredictionManager.class);
+        if (apm == null) {
+            return;
+        }
+        registerHotseatPredictor(apm, context);
+    }
+
+    private void registerHotseatPredictor(AppPredictionManager apm, Context context) {
+        registerPredictor(mHotseatState, apm.createAppPredictionSession(
+                new AppPredictionContext.Builder(context)
+                        .setUiSurface("hotseat")
+                        .setPredictedTargetCount(mIDP.numDatabaseHotseatIcons)
+                        .setExtras(convertDataModelToAppTargetBundle(context, mDataModel))
+                        .build()));
+    }
+
     private void registerPredictor(PredictorState state, AppPredictor predictor) {
         state.setTargets(Collections.emptyList());
         state.predictor = predictor;
@@ -393,7 +415,8 @@
         mWidgetsRecommendationState.predictor.requestPredictionUpdate();
     }
 
-    private void onAppTargetEvent(AppTargetEvent event, int client) {
+    @VisibleForTesting
+    void onAppTargetEvent(AppTargetEvent event, int client) {
         PredictorState state;
         switch(client) {
             case CONTAINER_PREDICTION:
@@ -411,6 +434,13 @@
             state.predictor.notifyAppTargetEvent(event);
             Log.d(TAG, "notifyAppTargetEvent action=" + event.getAction()
                     + " launchLocation=" + event.getLaunchLocation());
+            if (state == mHotseatState
+                    && (event.getAction() == AppTargetEvent.ACTION_PIN
+                            || event.getAction() == AppTargetEvent.ACTION_UNPIN)) {
+                // Recreate hot seat predictor when we need to query for hot seat due to pin or
+                // unpin app icons.
+                recreateHotseatPredictor();
+            }
         }
     }
 
diff --git a/quickstep/src/com/android/launcher3/proxy/ProxyActivityStarter.java b/quickstep/src/com/android/launcher3/proxy/ProxyActivityStarter.java
index 4d7cc85..6d90b035 100644
--- a/quickstep/src/com/android/launcher3/proxy/ProxyActivityStarter.java
+++ b/quickstep/src/com/android/launcher3/proxy/ProxyActivityStarter.java
@@ -24,6 +24,8 @@
 import android.os.Bundle;
 import android.util.Log;
 
+import com.android.launcher3.util.StartActivityParams;
+
 public class ProxyActivityStarter extends Activity {
 
     private static final String TAG = "ProxyActivityStarter";
diff --git a/quickstep/src/com/android/launcher3/secondarydisplay/SecondaryDisplayPredictionsImpl.java b/quickstep/src/com/android/launcher3/secondarydisplay/SecondaryDisplayPredictionsImpl.java
index 8720bd8..b982688 100644
--- a/quickstep/src/com/android/launcher3/secondarydisplay/SecondaryDisplayPredictionsImpl.java
+++ b/quickstep/src/com/android/launcher3/secondarydisplay/SecondaryDisplayPredictionsImpl.java
@@ -18,9 +18,7 @@
 import static com.android.launcher3.util.OnboardingPrefs.ALL_APPS_VISITED_COUNT;
 
 import android.content.Context;
-import android.view.View;
 
-import com.android.launcher3.allapps.ActivityAllAppsContainerView;
 import com.android.launcher3.appprediction.AppsDividerView;
 import com.android.launcher3.appprediction.PredictionRowView;
 import com.android.launcher3.model.BgDataModel;
@@ -56,12 +54,4 @@
                 .findFixedRowByType(PredictionRowView.class)
                 .setPredictedApps(item.items);
     }
-
-    @Override
-    public void setLongClickListener(ActivityAllAppsContainerView<?> appsView,
-            View.OnLongClickListener onIconLongClickListener) {
-        appsView.getFloatingHeaderView()
-                .findFixedRowByType(PredictionRowView.class)
-                .setOnIconLongClickListener(onIconLongClickListener);
-    }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 35b9957..4e834ec 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -50,6 +50,7 @@
 import com.android.quickstep.views.RecentsView;
 
 import java.io.PrintWriter;
+import java.util.Arrays;
 
 /**
  * A data source which integrates with a Launcher instance
@@ -105,6 +106,10 @@
 
         // Restore the in-app display progress from before Taskbar was recreated.
         float[] prevProgresses = mControllers.getSharedState().inAppDisplayProgressMultiPropValues;
+        // Make a copy of the previous progress to set since updating the multiprop will update
+        // the property which also calls onInAppDisplayProgressChanged() which writes the current
+        // values into the shared state
+        prevProgresses = Arrays.copyOf(prevProgresses, prevProgresses.length);
         for (int i = 0; i < prevProgresses.length; i++) {
             mTaskbarInAppDisplayProgressMultiProp.get(i).setValue(prevProgresses[i]);
         }
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index 10ae97b..1705f11 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -88,6 +88,7 @@
 import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory;
 import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory.NavButtonLayoutter;
 import com.android.launcher3.util.DimensionUtils;
+import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
 import com.android.launcher3.util.MultiValueAlpha;
 import com.android.launcher3.util.TouchController;
@@ -197,6 +198,7 @@
             this::onComputeInsetsForSeparateWindow;
     private final RecentsHitboxExtender mHitboxExtender = new RecentsHitboxExtender();
     private ImageView mRecentsButton;
+    private DisplayController mDisplayController;
 
     public NavbarButtonsViewController(TaskbarActivityContext context, FrameLayout navButtonsView) {
         mContext = context;
@@ -226,6 +228,8 @@
                         TaskbarManager.isPhoneMode(deviceProfile));
         mNavButtonsView.getLayoutParams().height = p.y;
 
+        mDisplayController = DisplayController.INSTANCE.get(mContext);
+
         mIsImeRenderingNavButtons =
                 InputMethodService.canImeRenderGesturalNavButtons() && mContext.imeDrawsImeNavBar();
         if (!mIsImeRenderingNavButtons) {
@@ -727,14 +731,10 @@
         boolean isInKidsMode = mContext.isNavBarKidsModeActive();
 
         if (TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW) {
-            if (!isThreeButtonNav) {
-                return;
-            }
-
             NavButtonLayoutter navButtonLayoutter =
                     NavButtonLayoutFactory.Companion.getUiLayoutter(
                             dp, mNavButtonsView, res, isInKidsMode, isInSetup, isThreeButtonNav,
-                            TaskbarManager.isPhoneMode(dp));
+                            TaskbarManager.isPhoneMode(dp), mDisplayController.getInfo().rotation);
             navButtonLayoutter.layoutButtons(dp, isContextualButtonShowing());
             return;
         }
@@ -927,6 +927,15 @@
         insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
     }
 
+    /**
+     * Called whenever a new ui controller is set, and should update anything that depends on the
+     * ui controller.
+     */
+    public void onUiControllerChanged() {
+        updateNavButtonInAppDisplayProgressForSysui();
+        updateNavButtonTranslationY();
+    }
+
     @Override
     public void dumpLogs(String prefix, PrintWriter pw) {
         pw.println(prefix + "NavbarButtonsViewController:");
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 43aceec..42cb290 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -55,6 +55,7 @@
 import android.view.Display;
 import android.view.Gravity;
 import android.view.RoundedCorner;
+import android.view.Surface;
 import android.view.View;
 import android.view.WindowManager;
 import android.widget.FrameLayout;
@@ -295,8 +296,7 @@
 
     public void init(@NonNull TaskbarSharedState sharedState) {
         mLastRequestedNonFullscreenHeight = getDefaultTaskbarWindowHeight();
-        mWindowLayoutParams =
-                createDefaultWindowLayoutParams(TYPE_NAVIGATION_BAR_PANEL, WINDOW_TITLE);
+        mWindowLayoutParams = createAllWindowParams();
 
         // Initialize controllers after all are constructed.
         mControllers.init(sharedState);
@@ -360,11 +360,6 @@
      * @param title The window title to pass to the created WindowManager.LayoutParams.
      */
     public WindowManager.LayoutParams createDefaultWindowLayoutParams(int type, String title) {
-        DeviceProfile deviceProfile = getDeviceProfile();
-        // Taskbar is on the logical bottom of the screen
-        boolean isVerticalBarLayout = TaskbarManager.isPhoneButtonNavMode(this) &&
-                deviceProfile.isLandscape;
-
         int windowFlags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                 | WindowManager.LayoutParams.FLAG_SLIPPERY
                 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
@@ -373,17 +368,14 @@
                     | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
         }
         WindowManager.LayoutParams windowLayoutParams = new WindowManager.LayoutParams(
-                isVerticalBarLayout ? mLastRequestedNonFullscreenHeight : MATCH_PARENT,
-                isVerticalBarLayout ? MATCH_PARENT : mLastRequestedNonFullscreenHeight,
+                MATCH_PARENT,
+                mLastRequestedNonFullscreenHeight,
                 type,
                 windowFlags,
                 PixelFormat.TRANSLUCENT);
         windowLayoutParams.setTitle(title);
         windowLayoutParams.packageName = getPackageName();
-        windowLayoutParams.gravity = !isVerticalBarLayout ?
-                Gravity.BOTTOM :
-                Gravity.END; // TODO(b/230394142): seascape
-
+        windowLayoutParams.gravity = Gravity.BOTTOM;
         windowLayoutParams.setFitInsetsTypes(0);
         windowLayoutParams.receiveInsetsIgnoringZOrder = true;
         windowLayoutParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
@@ -394,6 +386,64 @@
                 TaskbarManager.isPhoneMode(mDeviceProfile)
                         ? R.string.taskbar_phone_a11y_title
                         : R.string.taskbar_a11y_title);
+
+        return windowLayoutParams;
+    }
+
+    /**
+     * Creates {@link WindowManager.LayoutParams} for Taskbar, and also sets LP.paramsForRotation
+     * for taskbar showing as navigation bar
+     */
+    private WindowManager.LayoutParams createAllWindowParams() {
+        WindowManager.LayoutParams windowLayoutParams =
+                createDefaultWindowLayoutParams(TYPE_NAVIGATION_BAR_PANEL,
+                        TaskbarActivityContext.WINDOW_TITLE);
+        boolean isPhoneNavMode = TaskbarManager.isPhoneButtonNavMode(this);
+        if (!isPhoneNavMode) {
+            return windowLayoutParams;
+        }
+
+        // Provide WM layout params for all rotations to cache, see NavigationBar#getBarLayoutParams
+        int width = WindowManager.LayoutParams.MATCH_PARENT;
+        int height = WindowManager.LayoutParams.MATCH_PARENT;
+        int gravity = Gravity.BOTTOM;
+        windowLayoutParams.paramsForRotation = new WindowManager.LayoutParams[4];
+        for (int rot = Surface.ROTATION_0; rot <= Surface.ROTATION_270; rot++) {
+            WindowManager.LayoutParams lp =
+                    createDefaultWindowLayoutParams(TYPE_NAVIGATION_BAR_PANEL,
+                            TaskbarActivityContext.WINDOW_TITLE);
+            switch (rot) {
+                case Surface.ROTATION_0, Surface.ROTATION_180 -> {
+                    // Defaults are fine
+                    width = WindowManager.LayoutParams.MATCH_PARENT;
+                    height = mLastRequestedNonFullscreenHeight;
+                    gravity = Gravity.BOTTOM;
+                }
+                case Surface.ROTATION_90 -> {
+                    width = mLastRequestedNonFullscreenHeight;
+                    height = WindowManager.LayoutParams.MATCH_PARENT;
+                    gravity = Gravity.END;
+                }
+                case Surface.ROTATION_270 -> {
+                    width = mLastRequestedNonFullscreenHeight;
+                    height = WindowManager.LayoutParams.MATCH_PARENT;
+                    gravity = Gravity.START;
+                }
+
+            }
+            lp.width = width;
+            lp.height = height;
+            lp.gravity = gravity;
+            windowLayoutParams.paramsForRotation[rot] = lp;
+        }
+
+        // Override current layout params
+        WindowManager.LayoutParams currentParams =
+                windowLayoutParams.paramsForRotation[getDisplay().getRotation()];
+        windowLayoutParams.width = currentParams.width;
+        windowLayoutParams.height = currentParams.height;
+        windowLayoutParams.gravity = currentParams.gravity;
+
         return windowLayoutParams;
     }
 
@@ -593,9 +643,7 @@
      * Sets a new data-source for this taskbar instance
      */
     public void setUIController(@NonNull TaskbarUIController uiController) {
-        mControllers.uiController.onDestroy();
-        mControllers.uiController = uiController;
-        mControllers.uiController.init(mControllers);
+        mControllers.setUiController(uiController);
     }
 
     /**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
index 3cd151d..d82f501 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
@@ -196,6 +196,19 @@
         mPostInitCallbacks.clear();
     }
 
+    /**
+     * Sets the ui controller.
+     */
+    public void setUiController(@NonNull TaskbarUIController newUiController) {
+        uiController.onDestroy();
+        uiController = newUiController;
+        uiController.init(this);
+        uiController.updateStateForSysuiFlags(mSharedState.sysuiStateFlags);
+
+        // Notify that the ui controller has changed
+        navbarButtonsViewController.onUiControllerChanged();
+    }
+
     @Nullable
     public TaskbarSharedState getSharedState() {
         // This should only be null if called before init() and after destroy().
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
index 07cd8ff..68ea475 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
@@ -19,6 +19,7 @@
 import android.graphics.Region
 import android.os.Binder
 import android.os.IBinder
+import android.view.Gravity
 import android.view.InsetsFrameProvider
 import android.view.InsetsFrameProvider.SOURCE_DISPLAY
 import android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER
@@ -109,23 +110,21 @@
                         .setSource(SOURCE_DISPLAY)
                 )
         } else {
-            windowLayoutParams.providedInsets =
-                arrayOf(
-                    InsetsFrameProvider(insetsOwner, 0, navigationBars())
-                        .setFlags(
-                            insetsRoundedCornerFlag,
-                            (FLAG_SUPPRESS_SCRIM or FLAG_INSETS_ROUNDED_CORNER)
-                        ),
-                    InsetsFrameProvider(insetsOwner, 0, tappableElement()),
-                    InsetsFrameProvider(insetsOwner, 0, mandatorySystemGestures())
-                )
+            windowLayoutParams.providedInsets = getButtonNavInsets(insetsRoundedCornerFlag)
+            if (windowLayoutParams.paramsForRotation != null) {
+                for (layoutParams in windowLayoutParams.paramsForRotation) {
+                    layoutParams.providedInsets = getButtonNavInsets(insetsRoundedCornerFlag)
+                }
+            }
         }
 
         val taskbarTouchableHeight = controllers.taskbarStashController.touchableHeight
         val bubblesTouchableHeight =
-            if (controllers.bubbleControllers.isPresent)
+            if (controllers.bubbleControllers.isPresent) {
                 controllers.bubbleControllers.get().bubbleStashController.touchableHeight
-            else 0
+            } else {
+                0
+            }
         val touchableHeight = Math.max(taskbarTouchableHeight, bubblesTouchableHeight)
 
         if (
@@ -148,75 +147,99 @@
                 windowLayoutParams.height
             )
         }
-        val contentHeight = controllers.taskbarStashController.contentHeightToReportToApps
-        val res = context.resources
+
+        val gravity = windowLayoutParams.gravity
         for (provider in windowLayoutParams.providedInsets) {
-            if (provider.type == navigationBars() || provider.type == mandatorySystemGestures()) {
-                provider.insetsSize = getInsetsByNavMode(contentHeight)
-            } else if (provider.type == tappableElement()) {
-                provider.insetsSize = getInsetsByNavMode(tappableHeight)
-            } else if (provider.type == systemGestures() && provider.index == INDEX_LEFT) {
-                provider.insetsSize =
-                    Insets.of(
-                        gestureNavSettingsObserver.getLeftSensitivityForCallingUser(res),
-                        0,
-                        0,
-                        0
-                    )
-            } else if (provider.type == systemGestures() && provider.index == INDEX_RIGHT) {
-                provider.insetsSize =
-                    Insets.of(
-                        0,
-                        0,
-                        gestureNavSettingsObserver.getRightSensitivityForCallingUser(res),
-                        0
-                    )
-            }
+            setProviderInsets(provider, gravity)
         }
 
-        val imeInsetsSize = getInsetsByNavMode(taskbarHeightForIme)
-        val insetsSizeOverride =
-            arrayOf(
-                InsetsFrameProvider.InsetsSizeOverride(TYPE_INPUT_METHOD, imeInsetsSize),
-            )
-        // Use 0 tappableElement insets for the VoiceInteractionWindow when gesture nav is enabled.
-        val visInsetsSizeForGestureNavTappableElement = getInsetsByNavMode(0)
-        val insetsSizeOverrideForGestureNavTappableElement =
-            arrayOf(
-                InsetsFrameProvider.InsetsSizeOverride(TYPE_INPUT_METHOD, imeInsetsSize),
-                InsetsFrameProvider.InsetsSizeOverride(
-                    TYPE_VOICE_INTERACTION,
-                    visInsetsSizeForGestureNavTappableElement
-                ),
-            )
-        for (provider in windowLayoutParams.providedInsets) {
-            if (context.isGestureNav && provider.type == tappableElement()) {
-                provider.insetsSizeOverrides = insetsSizeOverrideForGestureNavTappableElement
-            } else if (provider.type != systemGestures()) {
-                // We only override insets at the bottom of the screen
-                provider.insetsSizeOverrides = insetsSizeOverride
+        if (windowLayoutParams.paramsForRotation != null) {
+            // Add insets for navbar rotated params
+            for (layoutParams in windowLayoutParams.paramsForRotation) {
+                for (provider in layoutParams.providedInsets) {
+                    setProviderInsets(provider, layoutParams.gravity)
+                }
             }
         }
 
         context.notifyUpdateLayoutParams()
     }
 
+    private fun getButtonNavInsets(insetsRoundedCornerFlag: Int): Array<InsetsFrameProvider> {
+        return arrayOf(
+                    InsetsFrameProvider(insetsOwner, 0, navigationBars())
+                        .setFlags(
+                            insetsRoundedCornerFlag,
+                            (FLAG_SUPPRESS_SCRIM or FLAG_INSETS_ROUNDED_CORNER)
+                        ),
+                    InsetsFrameProvider(insetsOwner, 0, tappableElement()),
+                    InsetsFrameProvider(insetsOwner, 0, mandatorySystemGestures()))
+    }
+
+    private fun setProviderInsets(provider: InsetsFrameProvider, gravity: Int) {
+        val contentHeight = controllers.taskbarStashController.contentHeightToReportToApps
+        val tappableHeight = controllers.taskbarStashController.tappableHeightToReportToApps
+        val res = context.resources
+        if (provider.type == navigationBars() || provider.type == mandatorySystemGestures()) {
+            provider.insetsSize = getInsetsByNavMode(contentHeight, gravity)
+        } else if (provider.type == tappableElement()) {
+            provider.insetsSize = getInsetsByNavMode(tappableHeight, gravity)
+        } else if (provider.type == systemGestures() && provider.index == INDEX_LEFT) {
+            provider.insetsSize =
+                    Insets.of(
+                            gestureNavSettingsObserver.getLeftSensitivityForCallingUser(res),
+                            0,
+                            0,
+                            0
+                    )
+        } else if (provider.type == systemGestures() && provider.index == INDEX_RIGHT) {
+            provider.insetsSize =
+                    Insets.of(
+                            0,
+                            0,
+                            gestureNavSettingsObserver.getRightSensitivityForCallingUser(res),
+                            0
+                    )
+        }
+
+        val imeInsetsSize = getInsetsByNavMode(taskbarHeightForIme, gravity)
+        val insetsSizeOverride =
+                arrayOf(
+                        InsetsFrameProvider.InsetsSizeOverride(TYPE_INPUT_METHOD, imeInsetsSize),
+                )
+        // Use 0 tappableElement insets for the VoiceInteractionWindow when gesture nav is enabled.
+        val visInsetsSizeForGestureNavTappableElement = getInsetsByNavMode(0, gravity)
+        val insetsSizeOverrideForGestureNavTappableElement =
+                arrayOf(
+                        InsetsFrameProvider.InsetsSizeOverride(TYPE_INPUT_METHOD, imeInsetsSize),
+                        InsetsFrameProvider.InsetsSizeOverride(
+                                TYPE_VOICE_INTERACTION,
+                                visInsetsSizeForGestureNavTappableElement
+                        ),
+                )
+        if (context.isGestureNav && provider.type == tappableElement()) {
+            provider.insetsSizeOverrides = insetsSizeOverrideForGestureNavTappableElement
+        } else if (provider.type != systemGestures()) {
+            // We only override insets at the bottom of the screen
+            provider.insetsSizeOverrides = insetsSizeOverride
+        }
+    }
+
     /**
-     * @return [Insets] where the [bottomInset] is either used as a bottom inset or
-     *
-     * ```
-     *         right/left inset if using 3 button nav
-     * ```
+     * @return [Insets] where the [inset] is either used as a bottom inset or
+     * right/left inset if using 3 button nav
      */
-    private fun getInsetsByNavMode(bottomInset: Int): Insets {
-        val devicePortrait = !context.deviceProfile.isLandscape
-        if (!TaskbarManager.isPhoneButtonNavMode(context) || devicePortrait) {
+    private fun getInsetsByNavMode(inset: Int, gravity: Int): Insets {
+        if ((gravity and Gravity.BOTTOM) != 0) {
             // Taskbar or portrait phone mode
-            return Insets.of(0, 0, 0, bottomInset)
+            return Insets.of(0, 0, 0, inset)
         }
 
         // TODO(b/230394142): seascape
-        return Insets.of(0, 0, bottomInset, 0)
+        val isSeascape = (gravity and Gravity.START) != 0
+        val leftInset = if (isSeascape) inset else 0
+        val rightInset = if (isSeascape) 0 else inset
+        return Insets.of(leftInset , 0, rightInset, 0)
     }
 
     /**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index 0b822fb..4ad3d5a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -202,6 +202,11 @@
                 public void onStateTransitionComplete(LauncherState finalState) {
                     mLauncherState = finalState;
                     updateStateForFlag(FLAG_LAUNCHER_IN_STATE_TRANSITION, false);
+                    // TODO(b/279514548) Cleans up bad state that can occur when user interacts with
+                    // taskbar on top of transparent activity.
+                    if (finalState == LauncherState.NORMAL && mLauncher.isResumed()) {
+                        updateStateForFlag(FLAG_RESUMED, true);
+                    }
                     applyState();
                     boolean disallowLongClick = finalState == LauncherState.OVERVIEW_SPLIT_SELECT;
                     com.android.launcher3.taskbar.Utilities.setOverviewDragState(
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index 8f3898f..1809d40 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -206,7 +206,14 @@
                         destroyExistingTaskbar();
                     } else {
                         if (dp != null && isTaskbarPresent(dp)) {
-                            mTaskbarActivityContext.updateDeviceProfile(dp);
+                            if (FLAG_HIDE_NAVBAR_WINDOW) {
+                                // Re-initialize for screen size change? Should this be done
+                                // by looking at screen-size change flag in configDiff in the
+                                // block above?
+                                recreateTaskbar();
+                            } else {
+                                mTaskbarActivityContext.updateDeviceProfile(dp);
+                            }
                         }
                         mTaskbarActivityContext.onConfigurationChanged(configDiff);
                     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
index 5eec726..512b77a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
@@ -177,7 +177,6 @@
                     systemShortcuts);
         }
 
-        icon.clearAccessibilityFocus();
         container.addOnAttachStateChangeListener(
                 new PopupLiveUpdateHandler<BaseTaskbarContext>(context, container) {
                     @Override
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 29f4f38..1b45404 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -25,6 +25,7 @@
 import static com.android.launcher3.anim.AnimatedFloat.VALUE;
 import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_ALLAPPS_BUTTON_TAP;
+import static com.android.launcher3.taskbar.TaskbarManager.isPhoneButtonNavMode;
 import static com.android.launcher3.taskbar.TaskbarManager.isPhoneMode;
 import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
 import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_TASKBAR_ALIGNMENT_ANIM;
@@ -171,6 +172,13 @@
                 .getTaskbarNavButtonTranslationYForInAppDisplay();
 
         mActivity.addOnDeviceProfileChangeListener(mDeviceProfileChangeListener);
+
+        if (TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW) {
+            // This gets modified in NavbarButtonsViewController, but the initial value it reads
+            // may be incorrect since it's state gets destroyed on taskbar recreate, so reset here
+            mTaskbarIconAlpha.get(ALPHA_INDEX_SMALL_SCREEN)
+                    .animateToValue(isPhoneButtonNavMode(mActivity) ? 0 : 1).start();
+        }
     }
 
     /**
@@ -444,8 +452,13 @@
      * Creates an animation for aligning the Taskbar icons with the provided Launcher device profile
      */
     private AnimatorPlaybackController createIconAlignmentController(DeviceProfile launcherDp) {
-        mOnControllerPreCreateCallback.run();
         PendingAnimation setter = new PendingAnimation(100);
+        if (TaskbarManager.isPhoneButtonNavMode(mActivity)) {
+            // No animation for icons in small-screen
+            return setter.createPlaybackController();
+        }
+
+        mOnControllerPreCreateCallback.run();
         DeviceProfile taskbarDp = mActivity.getDeviceProfile();
         Rect hotseatPadding = launcherDp.getHotseatLayoutPadding(mActivity);
         float scaleUp = ((float) launcherDp.iconSizePx) / taskbarDp.taskbarIconSize;
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
index 02d9d95..cf0b36a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
@@ -112,9 +112,6 @@
     /** Updates the current search suggestions. */
     public void setZeroStateSearchSuggestions(List<ItemInfo> zeroStateSearchSuggestions) {
         mZeroStateSearchSuggestions = zeroStateSearchSuggestions;
-        if (mSearchSessionController != null) {
-            mSearchSessionController.setZeroStateSearchSuggestions(zeroStateSearchSuggestions);
-        }
     }
 
     /** Updates the current notification dots. */
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java
index 01342af..a851734 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java
@@ -20,7 +20,6 @@
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.appprediction.AppsDividerView;
-import com.android.launcher3.appprediction.PredictionRowView;
 import com.android.launcher3.taskbar.NavbarButtonsViewController;
 import com.android.launcher3.taskbar.TaskbarControllers;
 import com.android.launcher3.taskbar.TaskbarStashController;
@@ -54,7 +53,6 @@
         mOverlayController = taskbarControllers.taskbarOverlayController;
 
         mSlideInView.init(new TaskbarAllAppsCallbacks());
-        setUpIconLongClick();
         setUpAppDivider();
         setUpTaskbarStashing();
     }
@@ -69,15 +67,6 @@
         mSlideInView.close(animate);
     }
 
-    private void setUpIconLongClick() {
-        mAppsView.setOnIconLongClickListener(
-                mContext.getDragController()::startDragOnLongClick);
-        mAppsView.getFloatingHeaderView()
-                .findFixedRowByType(PredictionRowView.class)
-                .setOnIconLongClickListener(
-                        mContext.getDragController()::startDragOnLongClick);
-    }
-
     private void setUpAppDivider() {
         mAppsView.getFloatingHeaderView()
                 .findFixedRowByType(AppsDividerView.class)
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
index 6b5c962..6818db6 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
@@ -31,8 +31,11 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
 
+import static java.lang.Math.abs;
+
 import android.annotation.BinderThread;
 import android.annotation.Nullable;
+import android.app.Notification;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.LauncherApps;
@@ -118,6 +121,7 @@
     private final Executor mMainExecutor;
     private final LauncherApps mLauncherApps;
     private final BubbleIconFactory mIconFactory;
+    private final SystemUiProxy mSystemUiProxy;
 
     private BubbleBarItem mSelectedBubble;
     private BubbleBarOverflow mOverflowBubble;
@@ -159,8 +163,10 @@
         mContext = context;
         mBarView = bubbleView; // Need the view for inflating bubble views.
 
+        mSystemUiProxy = SystemUiProxy.INSTANCE.get(context);
+
         if (BUBBLE_BAR_ENABLED) {
-            SystemUiProxy.INSTANCE.get(context).setBubblesListener(this);
+            mSystemUiProxy.setBubblesListener(this);
         }
         mMainExecutor = MAIN_EXECUTOR;
         mLauncherApps = context.getSystemService(LauncherApps.class);
@@ -173,7 +179,7 @@
     }
 
     public void onDestroy() {
-        SystemUiProxy.INSTANCE.get(mContext).setBubblesListener(null);
+        mSystemUiProxy.setBubblesListener(null);
     }
 
     public void init(TaskbarControllers controllers, BubbleControllers bubbleControllers) {
@@ -184,6 +190,8 @@
         bubbleControllers.runAfterInit(() -> {
             mBubbleBarViewController.setHiddenForBubbles(!BUBBLE_BAR_ENABLED);
             mBubbleStashedHandleViewController.setHiddenForBubbles(!BUBBLE_BAR_ENABLED);
+            mBubbleBarViewController.setUpdateSelectedBubbleAfterCollapse(
+                    key -> setSelectedBubble(mBubbles.get(key)));
         });
     }
 
@@ -238,17 +246,21 @@
             BUBBLE_STATE_EXECUTOR.execute(() -> {
                 createAndAddOverflowIfNeeded();
                 if (update.addedBubble != null) {
-                    viewUpdate.addedBubble = populateBubble(update.addedBubble, mContext, mBarView);
+                    viewUpdate.addedBubble = populateBubble(mContext, update.addedBubble, mBarView,
+                            null /* existingBubble */);
                 }
                 if (update.updatedBubble != null) {
+                    BubbleBarBubble existingBubble = mBubbles.get(update.updatedBubble.getKey());
                     viewUpdate.updatedBubble =
-                            populateBubble(update.updatedBubble, mContext, mBarView);
+                            populateBubble(mContext, update.updatedBubble, mBarView,
+                                    existingBubble);
                 }
                 if (update.currentBubbleList != null && !update.currentBubbleList.isEmpty()) {
                     List<BubbleBarBubble> currentBubbles = new ArrayList<>();
                     for (int i = 0; i < update.currentBubbleList.size(); i++) {
                         BubbleBarBubble b =
-                                populateBubble(update.currentBubbleList.get(i), mContext, mBarView);
+                                populateBubble(mContext, update.currentBubbleList.get(i), mBarView,
+                                        null /* existingBubble */);
                         currentBubbles.add(b);
                     }
                     viewUpdate.currentBubbles = currentBubbles;
@@ -310,9 +322,11 @@
         mBubbleStashedHandleViewController.setHiddenForBubbles(mBubbles.isEmpty());
 
         if (update.updatedBubble != null) {
-            // TODO: (b/269670235) handle updates:
-            //  (1) if content / icons change -- requires reload & add back in place
-            //  (2) if showing update dot changes -- tell the view to hide / show the dot
+            // Updates mean the dot state may have changed; any other changes were updated in
+            // the populateBubble step.
+            BubbleBarBubble bb = mBubbles.get(update.updatedBubble.getKey());
+            // If we're not stashed, we're visible so animate
+            bb.getView().updateDotVisibility(!mBubbleStashController.isStashed() /* animate */);
         }
         if (update.bubbleKeysInOrder != null && !update.bubbleKeysInOrder.isEmpty()) {
             // Create the new list
@@ -329,8 +343,8 @@
             // TODO: (b/273316505) handle suppression
         }
         if (update.selectedBubbleKey != null) {
-            if (mSelectedBubble != null
-                    && !update.selectedBubbleKey.equals(mSelectedBubble.getKey())) {
+            if (mSelectedBubble == null
+                    || !update.selectedBubbleKey.equals(mSelectedBubble.getKey())) {
                 BubbleBarBubble newlySelected = mBubbles.get(update.selectedBubbleKey);
                 if (newlySelected != null) {
                     bubbleToSelect = newlySelected;
@@ -352,12 +366,38 @@
         }
     }
 
+    /** Tells WMShell to show the currently selected bubble. */
+    public void showSelectedBubble() {
+        if (getSelectedBubbleKey() != null) {
+            if (mSelectedBubble instanceof BubbleBarBubble) {
+                // Because we've visited this bubble, we should suppress the notification.
+                // This is updated on WMShell side when we show the bubble, but that update isn't
+                // passed to launcher, instead we apply it directly here.
+                BubbleInfo info = ((BubbleBarBubble) mSelectedBubble).getInfo();
+                info.setFlags(
+                        info.getFlags() | Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION);
+                mSelectedBubble.getView().updateDotVisibility(true /* animate */);
+            }
+            mSystemUiProxy.showBubble(getSelectedBubbleKey(),
+                    getBubbleBarOffsetX(), getBubbleBarOffsetY());
+        } else {
+            Log.w(TAG, "Trying to show the selected bubble but it's null");
+        }
+    }
+
+    /** Updates the currently selected bubble for launcher views and tells WMShell to show it. */
+    public void showAndSelectBubble(BubbleBarItem b) {
+        if (DEBUG) Log.w(TAG, "showingSelectedBubble: " + b.getKey());
+        setSelectedBubble(b);
+        showSelectedBubble();
+    }
+
     /**
      * Sets the bubble that should be selected. This notifies the views, it does not notify
-     * WMShell that the selection has changed, that should go through
-     * {@link SystemUiProxy#showBubble}.
+     * WMShell that the selection has changed, that should go through either
+     * {@link #showSelectedBubble()} or {@link #showAndSelectBubble(BubbleBarItem)}.
      */
-    public void setSelectedBubble(BubbleBarItem b) {
+    private void setSelectedBubble(BubbleBarItem b) {
         if (!Objects.equals(b, mSelectedBubble)) {
             if (DEBUG) Log.w(TAG, "selectingBubble: " + b.getKey());
             mSelectedBubble = b;
@@ -381,7 +421,8 @@
     //
 
     @Nullable
-    private BubbleBarBubble populateBubble(BubbleInfo b, Context context, BubbleBarView bbv) {
+    private BubbleBarBubble populateBubble(Context context, BubbleInfo b, BubbleBarView bbv,
+            @Nullable BubbleBarBubble existingBubble) {
         String appName;
         Bitmap badgeBitmap;
         Bitmap bubbleBitmap;
@@ -450,16 +491,27 @@
         iconPath.transform(matrix);
         dotPath = iconPath;
         dotColor = ColorUtils.blendARGB(badgeBitmapInfo.color,
-                Color.WHITE, WHITE_SCRIM_ALPHA);
+                Color.WHITE, WHITE_SCRIM_ALPHA / 255f);
 
-        LayoutInflater inflater = LayoutInflater.from(context);
-        BubbleView bubbleView = (BubbleView) inflater.inflate(
-                R.layout.bubblebar_item_view, bbv, false /* attachToRoot */);
+        if (existingBubble == null) {
+            LayoutInflater inflater = LayoutInflater.from(context);
+            BubbleView bubbleView = (BubbleView) inflater.inflate(
+                    R.layout.bubblebar_item_view, bbv, false /* attachToRoot */);
 
-        BubbleBarBubble bubble = new BubbleBarBubble(b, bubbleView,
-                badgeBitmap, bubbleBitmap, dotColor, dotPath, appName);
-        bubbleView.setBubble(bubble);
-        return bubble;
+            BubbleBarBubble bubble = new BubbleBarBubble(b, bubbleView,
+                    badgeBitmap, bubbleBitmap, dotColor, dotPath, appName);
+            bubbleView.setBubble(bubble);
+            return bubble;
+        } else {
+            // If we already have a bubble (so it already has an inflated view), update it.
+            existingBubble.setInfo(b);
+            existingBubble.setBadge(badgeBitmap);
+            existingBubble.setIcon(bubbleBitmap);
+            existingBubble.setDotColor(dotColor);
+            existingBubble.setDotPath(dotPath);
+            existingBubble.setAppName(appName);
+            return existingBubble;
+        }
     }
 
     private BubbleBarOverflow createOverflow(Context context) {
@@ -494,4 +546,13 @@
 
         return mIconFactory.createBadgedIconBitmap(drawable).icon;
     }
+
+    private int getBubbleBarOffsetY() {
+        final int translation = (int) abs(mBubbleStashController.getBubbleBarTranslationY());
+        return translation + mBarView.getHeight();
+    }
+
+    private int getBubbleBarOffsetX() {
+        return mBarView.getWidth() + mBarView.getHorizontalMargin();
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarItem.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarItem.kt
index 582dcc7..43e21f4 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarItem.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarItem.kt
@@ -20,18 +20,18 @@
 import com.android.wm.shell.common.bubbles.BubbleInfo
 
 /** An entity in the bubble bar. */
-sealed class BubbleBarItem(open val key: String, open val view: BubbleView)
+sealed class BubbleBarItem(open var key: String, open var view: BubbleView)
 
 /** Contains state info about a bubble in the bubble bar as well as presentation information. */
 data class BubbleBarBubble(
-    val info: BubbleInfo,
-    override val view: BubbleView,
-    val badge: Bitmap,
-    val icon: Bitmap,
-    val dotColor: Int,
-    val dotPath: Path,
-    val appName: String
+    var info: BubbleInfo,
+    override var view: BubbleView,
+    var badge: Bitmap,
+    var icon: Bitmap,
+    var dotColor: Int,
+    var dotPath: Path,
+    var appName: String
 ) : BubbleBarItem(info.key, view)
 
 /** Represents the overflow bubble in the bubble bar. */
-data class BubbleBarOverflow(override val view: BubbleView) : BubbleBarItem("Overflow", view)
+data class BubbleBarOverflow(override var view: BubbleView) : BubbleBarItem("Overflow", view)
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
index 58c67e3..eec334a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
@@ -32,6 +32,7 @@
 import com.android.launcher3.views.ActivityContext;
 
 import java.util.List;
+import java.util.function.Consumer;
 
 /**
  * The view that holds all the bubble views. Modifying this view should happen through
@@ -71,7 +72,11 @@
 
     private final BubbleBarBackground mBubbleBarBackground;
 
-    // The current bounds of all the bubble bar.
+    /**
+     * The current bounds of all the bubble bar. Note that these bounds may not account for
+     * translation. The bounds should be retrieved using {@link #getBubbleBarBounds()} which
+     * updates the bounds and accounts for translation.
+     */
     private final Rect mBubbleBarBounds = new Rect();
     // The amount the bubbles overlap when they are stacked in the bubble bar
     private final float mIconOverlapAmount;
@@ -101,6 +106,9 @@
     @Nullable
     private Runnable mReorderRunnable;
 
+    @Nullable
+    private Consumer<String> mUpdateSelectedBubbleAfterCollapse;
+
     public BubbleBarView(Context context) {
         this(context, null);
     }
@@ -144,6 +152,13 @@
                     mReorderRunnable.run();
                     mReorderRunnable = null;
                 }
+                // If the bar was just collapsed and the overflow was the last bubble that was
+                // selected, set the first bubble as selected.
+                if (!mIsBarExpanded && mUpdateSelectedBubbleAfterCollapse != null
+                        && mSelectedBubbleView.getBubble() instanceof BubbleBarOverflow) {
+                    BubbleView firstBubble = (BubbleView) getChildAt(0);
+                    mUpdateSelectedBubbleAfterCollapse.accept(firstBubble.getBubble().getKey());
+                }
                 updateWidth();
             }
 
@@ -175,9 +190,11 @@
     }
 
     /**
-     * Returns the bounds of the bubble bar.
+     * Updates the bounds with translation that may have been applied and returns the result.
      */
     public Rect getBubbleBarBounds() {
+        mBubbleBarBounds.top = getTop() + (int) getTranslationY();
+        mBubbleBarBounds.bottom = getBottom() + (int) getTranslationY();
         return mBubbleBarBounds;
     }
 
@@ -206,6 +223,12 @@
         setLayoutParams(lp);
     }
 
+    /** @return the horizontal margin between the bubble bar and the edge of the screen. */
+    int getHorizontalMargin() {
+        LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
+        return lp.getMarginEnd();
+    }
+
     /**
      * Updates the z order, positions, and badge visibility of the bubble views in the bar based
      * on the expanded state.
@@ -217,6 +240,7 @@
         final float collapsedWidth = collapsedWidth();
         int bubbleCount = getChildCount();
         final float ty = (mBubbleBarBounds.height() - mIconSize) / 2f;
+        final boolean animate = getVisibility() == VISIBLE;
         for (int i = 0; i < bubbleCount; i++) {
             BubbleView bv = (BubbleView) getChildAt(i);
             bv.setTranslationY(ty);
@@ -234,16 +258,14 @@
                 if (widthState == 1f) {
                     bv.setZ(0);
                 }
-                bv.showBadge();
+                // When we're expanded, we're not stacked so we're not behind the stack
+                bv.setBehindStack(false, animate);
             } else {
                 final float targetX = currentWidth - collapsedWidth + collapsedX;
                 bv.setTranslationX(widthState * (expandedX - targetX) + targetX);
                 bv.setZ((MAX_BUBBLES * mBubbleElevation) - i);
-                if (i > 0) {
-                    bv.hideBadge();
-                } else {
-                    bv.showBadge();
-                }
+                // If we're not the first bubble we're behind the stack
+                bv.setBehindStack(i > 0, animate);
             }
         }
 
@@ -293,6 +315,11 @@
         }
     }
 
+    public void setUpdateSelectedBubbleAfterCollapse(
+            Consumer<String> updateSelectedBubbleAfterCollapse) {
+        mUpdateSelectedBubbleAfterCollapse = updateSelectedBubbleAfterCollapse;
+    }
+
     /**
      * Sets which bubble view should be shown as selected.
      */
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
index 52c144e..8e7fda8 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
@@ -36,6 +36,7 @@
 
 import java.util.List;
 import java.util.Objects;
+import java.util.function.Consumer;
 
 /**
  * Controller for {@link BubbleBarView}. Manages the visibility of the bubble bar as well as
@@ -111,9 +112,7 @@
             setExpanded(false);
             mBubbleStashController.stashBubbleBar();
         } else {
-            mBubbleBarController.setSelectedBubble(bubble);
-            mSystemUiProxy.showBubble(bubble.getKey(),
-                    mBubbleStashController.isBubblesShowingOnHome());
+            mBubbleBarController.showAndSelectBubble(bubble);
         }
     }
 
@@ -141,6 +140,11 @@
         return mBarView.getVisibility() == VISIBLE;
     }
 
+    /** Whether the bubble bar has bubbles. */
+    public boolean hasBubbles() {
+        return mBubbleBarController.getSelectedBubbleKey() != null;
+    }
+
     /**
      * The bounds of the bubble bar.
      */
@@ -184,6 +188,12 @@
         }
     }
 
+    /** Sets a callback that updates the selected bubble after the bubble bar collapses. */
+    public void setUpdateSelectedBubbleAfterCollapse(
+            Consumer<String> updateSelectedBubbleAfterCollapse) {
+        mBarView.setUpdateSelectedBubbleAfterCollapse(updateSelectedBubbleAfterCollapse);
+    }
+
     /**
      * Sets whether the bubble bar should be hidden due to SysUI state (e.g. on lockscreen).
      */
@@ -284,13 +294,7 @@
             if (!isExpanded) {
                 mSystemUiProxy.collapseBubbles();
             } else {
-                final String selectedKey = mBubbleBarController.getSelectedBubbleKey();
-                if (selectedKey != null) {
-                    mSystemUiProxy.showBubble(selectedKey,
-                            mBubbleStashController.isBubblesShowingOnHome());
-                } else {
-                    Log.w(TAG, "trying to expand bubbles when there isn't one selected");
-                }
+                mBubbleBarController.showSelectedBubble();
                 mTaskbarStashController.updateAndAnimateTransientTaskbar(true /* stash */,
                         false /* shouldBubblesFollow */);
             }
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java
index b3c7d41..8af4ff9 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3.taskbar.bubbles;
 
+import static java.lang.Math.abs;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
@@ -123,8 +125,24 @@
     public void setBubblesShowingOnHome(boolean onHome) {
         if (mBubblesShowingOnHome != onHome) {
             mBubblesShowingOnHome = onHome;
+
+            if (!mBarViewController.hasBubbles()) {
+                // if there are no bubbles, there's nothing to show, so just return.
+                return;
+            }
+
             if (mBubblesShowingOnHome) {
                 showBubbleBar(/* expanded= */ false);
+                // When transitioning from app to home the stash animator may already have been
+                // created, so we need to animate the bubble bar here to align with hotseat.
+                if (!mIsStashed) {
+                    mIconTranslationYForStash.animateToValue(getBubbleBarTranslationYForHotseat())
+                            .start();
+                }
+                // If the bubble bar is already unstashed, the taskbar touchable region won't be
+                // updated correctly, so force an update here.
+                mControllers.runAfterInit(() ->
+                        mTaskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged());
             } else if (!mBarViewController.isExpanded()) {
                 stashBubbleBar();
             }
@@ -143,6 +161,11 @@
             mBubblesShowingOnOverview = onOverview;
             if (!mBubblesShowingOnOverview && !mBarViewController.isExpanded()) {
                 stashBubbleBar();
+            } else {
+                // When transitioning to overview the stash animator may already have been
+                // created, so we need to animate the bubble bar here to align with taskbar.
+                mIconTranslationYForStash.animateToValue(getBubbleBarTranslationYForTaskbar())
+                        .start();
             }
         }
     }
@@ -234,8 +257,11 @@
             secondHalfDurationScale = 0.75f;
 
             // If we're on home, adjust the translation so the bubble bar aligns with hotseat.
-            final float hotseatTransY = mActivity.getDeviceProfile().getTaskbarOffsetY();
-            final float translationY = mBubblesShowingOnHome ? hotseatTransY : 0;
+            // Otherwise we're either showing in an app or in overview. In either case adjust it so
+            // the bubble bar aligns with the taskbar.
+            final float translationY = mBubblesShowingOnHome ? getBubbleBarTranslationYForHotseat()
+                    : getBubbleBarTranslationYForTaskbar();
+
             fullLengthAnimatorSet.playTogether(
                     mIconScaleForStash.animateToValue(1),
                     mIconTranslationYForStash.animateToValue(translationY));
@@ -265,6 +291,7 @@
                     if (isStashed) {
                         mBarViewController.setExpanded(false);
                     }
+                    mTaskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged();
                 });
             }
         });
@@ -277,4 +304,20 @@
             mTaskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged();
         });
     }
+
+    private float getBubbleBarTranslationYForTaskbar() {
+        return -mActivity.getDeviceProfile().taskbarBottomMargin;
+    }
+
+    private float getBubbleBarTranslationYForHotseat() {
+        final float hotseatBottomSpace = mActivity.getDeviceProfile().hotseatBarBottomSpacePx;
+        final float hotseatCellHeight = mActivity.getDeviceProfile().hotseatCellHeightPx;
+        return -hotseatBottomSpace - hotseatCellHeight + mUnstashedHeight - abs(
+                hotseatCellHeight - mUnstashedHeight) / 2;
+    }
+
+    float getBubbleBarTranslationY() {
+        return mBubblesShowingOnHome ? getBubbleBarTranslationYForHotseat()
+                : getBubbleBarTranslationYForTaskbar();
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
index 92b76a6..12cb8c5 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
@@ -18,7 +18,9 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.graphics.Bitmap;
+import android.graphics.Canvas;
 import android.graphics.Outline;
+import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -28,10 +30,13 @@
 import androidx.constraintlayout.widget.ConstraintLayout;
 
 import com.android.launcher3.R;
+import com.android.launcher3.icons.DotRenderer;
 import com.android.launcher3.icons.IconNormalizer;
+import com.android.wm.shell.animation.Interpolators;
+
+import java.util.EnumSet;
 
 // TODO: (b/276978250) This is will be similar to WMShell's BadgedImageView, it'd be nice to share.
-// TODO: (b/269670235) currently this doesn't show the 'update dot'
 
 /**
  * View that displays a bubble icon, along with an app badge on either the left or
@@ -39,14 +44,42 @@
  */
 public class BubbleView extends ConstraintLayout {
 
-    // TODO: (b/269670235) currently we don't render the 'update dot', this will be used for that.
     public static final int DEFAULT_PATH_SIZE = 100;
 
+    /**
+     * Flags that suppress the visibility of the 'new' dot or the app badge, for one reason or
+     * another. If any of these flags are set, the dot will not be shown.
+     * If {@link SuppressionFlag#BEHIND_STACK} then the app badge will not be shown.
+     */
+    enum SuppressionFlag {
+        // TODO: (b/277815200) implement flyout
+        // Suppressed because the flyout is visible - it will morph into the dot via animation.
+        FLYOUT_VISIBLE,
+        // Suppressed because this bubble is behind others in the collapsed stack.
+        BEHIND_STACK,
+    }
+
+    private final EnumSet<SuppressionFlag> mSuppressionFlags =
+            EnumSet.noneOf(SuppressionFlag.class);
+
     private final ImageView mBubbleIcon;
     private final ImageView mAppIcon;
     private final int mBubbleSize;
 
+    private DotRenderer mDotRenderer;
+    private DotRenderer.DrawParams mDrawParams;
+    private int mDotColor;
+    private Rect mTempBounds = new Rect();
+
+    // Whether the dot is animating
+    private boolean mDotIsAnimating;
+    // What scale value the dot is animating to
+    private float mAnimatingToDotScale;
+    // The current scale value of the dot
+    private float mDotScale;
+
     // TODO: (b/273310265) handle RTL
+    // Whether the bubbles are positioned on the left or right side of the screen
     private boolean mOnLeft = false;
 
     private BubbleBarItem mBubble;
@@ -75,6 +108,8 @@
         mBubbleIcon = findViewById(R.id.icon_view);
         mAppIcon = findViewById(R.id.app_icon_view);
 
+        mDrawParams = new DotRenderer.DrawParams();
+
         setFocusable(true);
         setClickable(true);
         setOutlineProvider(new ViewOutlineProvider() {
@@ -91,17 +126,43 @@
         outline.setOval(inset, inset, inset + normalizedSize, inset + normalizedSize);
     }
 
+    @Override
+    public void dispatchDraw(Canvas canvas) {
+        super.dispatchDraw(canvas);
+
+        if (!shouldDrawDot()) {
+            return;
+        }
+
+        getDrawingRect(mTempBounds);
+
+        mDrawParams.dotColor = mDotColor;
+        mDrawParams.iconBounds = mTempBounds;
+        mDrawParams.leftAlign = mOnLeft;
+        mDrawParams.scale = mDotScale;
+
+        mDotRenderer.draw(canvas, mDrawParams);
+    }
+
     /** Sets the bubble being rendered in this view. */
     void setBubble(BubbleBarBubble bubble) {
         mBubble = bubble;
         mBubbleIcon.setImageBitmap(bubble.getIcon());
         mAppIcon.setImageBitmap(bubble.getBadge());
+        mDotColor = bubble.getDotColor();
+        mDotRenderer = new DotRenderer(mBubbleSize, bubble.getDotPath(), DEFAULT_PATH_SIZE);
     }
 
+    /**
+     * Sets that this bubble represents the overflow. The overflow appears in the list of bubbles
+     * but does not represent app content, instead it shows recent bubbles that couldn't fit into
+     * the list of bubbles. It doesn't show an app icon because it is part of system UI / doesn't
+     * come from an app.
+     */
     void setOverflow(BubbleBarOverflow overflow, Bitmap bitmap) {
         mBubble = overflow;
         mBubbleIcon.setImageBitmap(bitmap);
-        hideBadge();
+        mAppIcon.setVisibility(GONE); // Overflow doesn't show the app badge
     }
 
     /** Returns the bubble being rendered in this view. */
@@ -110,38 +171,102 @@
         return mBubble;
     }
 
-    /** Shows the app badge on this bubble. */
-    void showBadge() {
+    void updateDotVisibility(boolean animate) {
+        final float targetScale = shouldDrawDot() ? 1f : 0f;
+        if (animate) {
+            animateDotScale();
+        } else {
+            mDotScale = targetScale;
+            mAnimatingToDotScale = targetScale;
+            invalidate();
+        }
+    }
+
+    void updateBadgeVisibility() {
         if (mBubble instanceof BubbleBarOverflow) {
             // The overflow bubble does not have a badge, so just bail.
             return;
         }
         BubbleBarBubble bubble = (BubbleBarBubble) mBubble;
-
         Bitmap appBadgeBitmap = bubble.getBadge();
-        if (appBadgeBitmap == null) {
-            mAppIcon.setVisibility(GONE);
+        int translationX = mOnLeft
+                ? -(bubble.getIcon().getWidth() - appBadgeBitmap.getWidth())
+                : 0;
+        mAppIcon.setTranslationX(translationX);
+        mAppIcon.setVisibility(isBehindStack() ? GONE : VISIBLE);
+    }
+
+    /** Sets whether this bubble is in the stack & not the first bubble. **/
+    void setBehindStack(boolean behindStack, boolean animate) {
+        if (behindStack) {
+            mSuppressionFlags.add(SuppressionFlag.BEHIND_STACK);
+        } else {
+            mSuppressionFlags.remove(SuppressionFlag.BEHIND_STACK);
+        }
+        updateDotVisibility(animate);
+        updateBadgeVisibility();
+    }
+
+    /** Whether this bubble is in the stack & not the first bubble. **/
+    boolean isBehindStack() {
+        return mSuppressionFlags.contains(SuppressionFlag.BEHIND_STACK);
+    }
+
+    /** Whether the dot indicating unseen content in a bubble should be shown. */
+    private boolean shouldDrawDot() {
+        boolean bubbleHasUnseenContent = mBubble != null
+                && mBubble instanceof BubbleBarBubble
+                && mSuppressionFlags.isEmpty()
+                && !((BubbleBarBubble) mBubble).getInfo().isNotificationSuppressed();
+
+        // Always render the dot if it's animating, since it could be animating out. Otherwise, show
+        // it if the bubble wants to show it, and we aren't suppressing it.
+        return bubbleHasUnseenContent || mDotIsAnimating;
+    }
+
+    /** How big the dot should be, fraction from 0 to 1. */
+    private void setDotScale(float fraction) {
+        mDotScale = fraction;
+        invalidate();
+    }
+
+    /**
+     * Animates the dot to the given scale.
+     */
+    private void animateDotScale() {
+        float toScale = shouldDrawDot() ? 1f : 0f;
+        mDotIsAnimating = true;
+
+        // Don't restart the animation if we're already animating to the given value.
+        if (mAnimatingToDotScale == toScale || !shouldDrawDot()) {
+            mDotIsAnimating = false;
             return;
         }
 
-        int translationX;
-        if (mOnLeft) {
-            translationX = -(bubble.getIcon().getWidth() - appBadgeBitmap.getWidth());
-        } else {
-            translationX = 0;
-        }
+        mAnimatingToDotScale = toScale;
 
-        mAppIcon.setTranslationX(translationX);
-        mAppIcon.setVisibility(VISIBLE);
+        final boolean showDot = toScale > 0f;
+
+        // Do NOT wait until after animation ends to setShowDot
+        // to avoid overriding more recent showDot states.
+        clearAnimation();
+        animate()
+                .setDuration(200)
+                .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
+                .setUpdateListener((valueAnimator) -> {
+                    float fraction = valueAnimator.getAnimatedFraction();
+                    fraction = showDot ? fraction : 1f - fraction;
+                    setDotScale(fraction);
+                }).withEndAction(() -> {
+                    setDotScale(showDot ? 1f : 0f);
+                    mDotIsAnimating = false;
+                }).start();
     }
 
-    /** Hides the app badge on this bubble. */
-    void hideBadge() {
-        mAppIcon.setVisibility(GONE);
-    }
 
     @Override
     public String toString() {
-        return "BubbleView{" + mBubble + "}";
+        String toString = mBubble != null ? mBubble.getKey() : "null";
+        return "BubbleView{" + toString + "}";
     }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt
index 27a4988..b682081 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt
@@ -17,10 +17,12 @@
 package com.android.launcher3.taskbar.navbutton
 
 import android.content.res.Resources
+import android.graphics.drawable.RotateDrawable
 import android.view.ViewGroup
 import android.widget.ImageView
 import android.widget.LinearLayout
 import com.android.launcher3.R
+import com.android.launcher3.Utilities
 import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory.NavButtonLayoutter
 
 /**
@@ -40,7 +42,18 @@
     protected val endContextualContainer: ViewGroup,
     protected val startContextualContainer: ViewGroup
 ) : NavButtonLayoutter {
-    protected val homeButton: ImageView = navButtonContainer.findViewById(R.id.home)
-    protected val recentsButton: ImageView = navButtonContainer.findViewById(R.id.recent_apps)
-    protected val backButton: ImageView = navButtonContainer.findViewById(R.id.back)
+    protected val homeButton: ImageView? = navButtonContainer.findViewById(R.id.home)
+    protected val recentsButton: ImageView? = navButtonContainer.findViewById(R.id.recent_apps)
+    protected val backButton: ImageView? = navButtonContainer.findViewById(R.id.back)
+
+    init {
+        // setup back button drawable
+        if (backButton != null) {
+            val rotateDrawable = RotateDrawable()
+            rotateDrawable.drawable = backButton.context?.getDrawable(R.drawable.ic_sysbar_back)
+            rotateDrawable.fromDegrees = 0f
+            rotateDrawable.toDegrees = if (Utilities.isRtl(backButton.resources)) 90f else -90f
+            backButton.setImageDrawable(rotateDrawable)
+        }
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt
index 468a1a7..4a53c0c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt
@@ -51,10 +51,10 @@
         val paddingTop = (buttonHeight - iconSize) / 2
 
         // Update icons
-        backButton.setImageDrawable(backButton.context.getDrawable(DRAWABLE_SYSBAR_BACK_KIDS))
+        backButton!!.setImageDrawable(backButton.context.getDrawable(DRAWABLE_SYSBAR_BACK_KIDS))
         backButton.scaleType = ImageView.ScaleType.FIT_CENTER
         backButton.setPadding(paddingLeft, paddingTop, paddingLeft, paddingTop)
-        homeButton.setImageDrawable(homeButton.getContext().getDrawable(DRAWABLE_SYSBAR_HOME_KIDS))
+        homeButton!!.setImageDrawable(homeButton.context.getDrawable(DRAWABLE_SYSBAR_HOME_KIDS))
         homeButton.scaleType = ImageView.ScaleType.FIT_CENTER
         homeButton.setPadding(paddingLeft, paddingTop, paddingLeft, paddingTop)
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt
index 0668da9..7db2320 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt
@@ -17,6 +17,8 @@
 package com.android.launcher3.taskbar.navbutton
 
 import android.content.res.Resources
+import android.view.Surface.ROTATION_90
+import android.view.Surface.Rotation
 import android.view.ViewGroup
 import android.widget.FrameLayout
 import android.widget.LinearLayout
@@ -56,7 +58,8 @@
             isKidsMode: Boolean,
             isInSetup: Boolean,
             isThreeButtonNav: Boolean,
-            phoneMode: Boolean
+            phoneMode: Boolean,
+            @Rotation surfaceRotation: Int
         ): NavButtonLayoutter {
             val navButtonContainer = navButtonsView.findViewById<LinearLayout>(ID_END_NAV_BUTTONS)
             val endContextualContainer =
@@ -73,13 +76,20 @@
                             endContextualContainer,
                             startContextualContainer
                         )
-                    } else {
+                    } else if (surfaceRotation == ROTATION_90) {
                         PhoneLandscapeNavLayoutter(
                             resources,
                             navButtonContainer,
                             endContextualContainer,
                             startContextualContainer
                         )
+                    } else {
+                        PhoneSeascapeNavLayoutter(
+                                resources,
+                                navButtonContainer,
+                                endContextualContainer,
+                                startContextualContainer
+                        )
                     }
                 }
                 deviceProfile.isTaskbarPresent -> {
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt
index 201895f..92715a7 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt
@@ -27,7 +27,7 @@
 import com.android.launcher3.taskbar.TaskbarManager
 import com.android.launcher3.util.DimensionUtils
 
-class PhoneLandscapeNavLayoutter(
+open class PhoneLandscapeNavLayoutter(
     resources: Resources,
     navBarContainer: LinearLayout,
     endContextualContainer: ViewGroup,
@@ -44,8 +44,8 @@
         // TODO(b/230395757): Polish pending, this is just to make it usable
         val navContainerParams = navButtonContainer.layoutParams as FrameLayout.LayoutParams
         val endStartMargins = resources.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size)
-        val taskbarDimensions =
-            DimensionUtils.getTaskbarPhoneDimensions(dp, resources, TaskbarManager.isPhoneMode(dp))
+        val taskbarDimensions = DimensionUtils.getTaskbarPhoneDimensions(dp, resources,
+                TaskbarManager.isPhoneMode(dp))
         navButtonContainer.removeAllViews()
         navButtonContainer.orientation = LinearLayout.VERTICAL
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt
index f7ac974..7f7fda7 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt
@@ -43,7 +43,8 @@
         // TODO(b/230395757): Polish pending, this is just to make it usable
         val navContainerParams = navButtonContainer.layoutParams as FrameLayout.LayoutParams
         val taskbarDimensions =
-            DimensionUtils.getTaskbarPhoneDimensions(dp, resources, TaskbarManager.isPhoneMode(dp))
+            DimensionUtils.getTaskbarPhoneDimensions(dp, resources,
+                    TaskbarManager.isPhoneMode(dp))
         val endStartMargins = resources.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size)
         navContainerParams.width = taskbarDimensions.x
         navContainerParams.height = ViewGroup.LayoutParams.MATCH_PARENT
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneSeascapeNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneSeascapeNavLayoutter.kt
new file mode 100644
index 0000000..f0fe581
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneSeascapeNavLayoutter.kt
@@ -0,0 +1,46 @@
+/*
+* Copyright (C) 2023 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*      http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License
+*/
+
+package com.android.launcher3.taskbar.navbutton
+
+import android.content.res.Resources
+import android.view.ViewGroup
+import android.widget.LinearLayout
+import com.android.launcher3.DeviceProfile
+
+class PhoneSeascapeNavLayoutter(
+        resources: Resources,
+        navBarContainer: LinearLayout,
+        endContextualContainer: ViewGroup,
+        startContextualContainer: ViewGroup
+) :
+        PhoneLandscapeNavLayoutter(
+                resources,
+                navBarContainer,
+                endContextualContainer,
+                startContextualContainer
+        ) {
+
+    override fun layoutButtons(dp: DeviceProfile, isContextualButtonShowing: Boolean) {
+        // TODO(b/230395757): Polish pending, this is just to make it usable
+        super.layoutButtons(dp, isContextualButtonShowing)
+        navButtonContainer.removeAllViews()
+        // Flip ordering of back and recents buttons
+        navButtonContainer.addView(backButton)
+        navButtonContainer.addView(homeButton)
+        navButtonContainer.addView(recentsButton)
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayContext.java b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayContext.java
index cfcc1a0..64cc47c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayContext.java
@@ -126,6 +126,11 @@
     }
 
     @Override
+    public View.OnLongClickListener getAllAppsItemLongClickListener() {
+        return mDragController::startDragOnLongClick;
+    }
+
+    @Override
     public PopupDataProvider getPopupDataProvider() {
         return mTaskbarContext.getPopupDataProvider();
     }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
index d241260..d78ca88 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
@@ -24,33 +24,26 @@
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_MODAL;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SPLIT_SELECT_FLOATING_TASK_TRANSLATE_OFFSCREEN;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SPLIT_SELECT_INSTRUCTIONS_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y;
 import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
-import static com.android.quickstep.views.FloatingTaskView.PRIMARY_TRANSLATE_OFFSCREEN;
 import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET;
 import static com.android.quickstep.views.RecentsView.RECENTS_GRID_PROGRESS;
 import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
 import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
 import static com.android.quickstep.views.RecentsView.TASK_THUMBNAIL_SPLASH_ALPHA;
 
-import android.graphics.Rect;
-import android.graphics.RectF;
 import android.util.FloatProperty;
 import android.view.animation.Interpolator;
 
 import androidx.annotation.NonNull;
 
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.statemanager.StateManager.StateHandler;
 import com.android.launcher3.states.StateAnimationConfig;
-import com.android.quickstep.views.FloatingTaskView;
 import com.android.quickstep.views.RecentsView;
 
 /**
@@ -115,49 +108,18 @@
                 config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, LINEAR));
 
         boolean exitingOverview = !FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()
-                || !toState.overviewUi;
+                && !toState.overviewUi;
         if (mRecentsView.isSplitSelectionActive() && exitingOverview) {
-            // TODO (b/238651489): Refactor state management to avoid need for double check
-            FloatingTaskView floatingTask = mRecentsView.getFirstFloatingTaskView();
-            if (floatingTask != null) {
-                // We are in split selection state currently, transitioning to another state
-                DragLayer dragLayer = mLauncher.getDragLayer();
-                RectF onScreenRectF = new RectF();
-                Utilities.getBoundsForViewInDragLayer(mLauncher.getDragLayer(), floatingTask,
-                        new Rect(0, 0, floatingTask.getWidth(), floatingTask.getHeight()),
-                        false, null, onScreenRectF);
-                // Get the part of the floatingTask that intersects with the DragLayer (i.e. the
-                // on-screen portion)
-                onScreenRectF.intersect(
-                        dragLayer.getLeft(),
-                        dragLayer.getTop(),
-                        dragLayer.getRight(),
-                        dragLayer.getBottom()
-                );
-
-                setter.setFloat(
-                        mRecentsView.getFirstFloatingTaskView(),
-                        PRIMARY_TRANSLATE_OFFSCREEN,
-                        mRecentsView.getPagedOrientationHandler()
-                                .getFloatingTaskOffscreenTranslationTarget(
-                                        floatingTask,
-                                        onScreenRectF,
-                                        floatingTask.getStagePosition(),
-                                        mLauncher.getDeviceProfile()
-                                ),
-                        config.getInterpolator(
-                                ANIM_OVERVIEW_SPLIT_SELECT_FLOATING_TASK_TRANSLATE_OFFSCREEN,
-                                LINEAR
-                        ));
-                setter.setViewAlpha(
-                        mRecentsView.getSplitInstructionsView(),
-                        0,
-                        config.getInterpolator(
-                                ANIM_OVERVIEW_SPLIT_SELECT_INSTRUCTIONS_FADE,
-                                LINEAR
-                        )
-                );
-            }
+            setter.add(mRecentsView.getSplitSelectController().getSplitAnimationController()
+                    .createPlaceholderDismissAnim(mLauncher));
+            setter.setViewAlpha(
+                    mRecentsView.getSplitInstructionsView(),
+                    0,
+                    config.getInterpolator(
+                            ANIM_OVERVIEW_SPLIT_SELECT_INSTRUCTIONS_FADE,
+                            LINEAR
+                    )
+            );
         }
 
         setter.setFloat(mRecentsView, getContentAlphaProperty(), toState.overviewUi ? 1 : 0,
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 582b795..fbe0a8f 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -33,7 +33,6 @@
 import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK;
 import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
 import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE;
 import static com.android.launcher3.config.FeatureFlags.RECEIVE_UNFOLD_EVENTS_FROM_SYSUI;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP;
@@ -120,7 +119,6 @@
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.proxy.ProxyActivityStarter;
-import com.android.launcher3.proxy.StartActivityParams;
 import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.statehandlers.DesktopVisibilityController;
 import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory;
@@ -152,6 +150,7 @@
 import com.android.launcher3.util.SplitConfigurationOptions;
 import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
 import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource;
+import com.android.launcher3.util.StartActivityParams;
 import com.android.launcher3.util.TouchController;
 import com.android.launcher3.widget.LauncherWidgetHolder;
 import com.android.quickstep.OverviewCommandHelper;
@@ -193,6 +192,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
+import java.util.function.Consumer;
 import java.util.function.Predicate;
 import java.util.stream.Stream;
 
@@ -405,8 +405,7 @@
     }
 
     private List<SystemShortcut.Factory<QuickstepLauncher>> getSplitShortcuts() {
-
-        if (!ENABLE_SPLIT_FROM_WORKSPACE.get() || !mDeviceProfile.isTablet) {
+        if (!mDeviceProfile.isTablet || mSplitSelectStateController.isSplitSelectActive()) {
             return Collections.emptyList();
         }
         RecentsView recentsView = getOverviewPanel();
@@ -545,11 +544,22 @@
 
         ArrayList<TouchController> list = new ArrayList<>();
         list.add(getDragController());
+        Consumer<AnimatorSet> splitAnimator = animatorSet -> {
+            AnimatorSet anim = mSplitSelectStateController.getSplitAnimationController()
+                    .createPlaceholderDismissAnim(QuickstepLauncher.this);
+            anim.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    mSplitSelectStateController.resetState();
+                }
+            });
+            animatorSet.play(anim);
+        };
         switch (mode) {
             case NO_BUTTON:
                 list.add(new NoButtonQuickSwitchTouchController(this));
-                list.add(new NavBarToHomeTouchController(this));
-                list.add(new NoButtonNavbarToOverviewTouchController(this));
+                list.add(new NavBarToHomeTouchController(this, splitAnimator));
+                list.add(new NoButtonNavbarToOverviewTouchController(this, splitAnimator));
                 break;
             case TWO_BUTTONS:
                 list.add(new TwoButtonNavbarTouchController(this));
@@ -560,8 +570,8 @@
                 break;
             case THREE_BUTTONS:
                 list.add(new NoButtonQuickSwitchTouchController(this));
-                list.add(new NavBarToHomeTouchController(this));
-                list.add(new NoButtonNavbarToOverviewTouchController(this));
+                list.add(new NavBarToHomeTouchController(this, splitAnimator));
+                list.add(new NoButtonNavbarToOverviewTouchController(this, splitAnimator));
                 list.add(new PortraitStatesTouchController(this));
                 break;
             default:
@@ -987,6 +997,13 @@
         return mSplitToWorkspaceController;
     }
 
+    @Override
+    protected void handleSplitAnimationGoingToHome() {
+        super.handleSplitAnimationGoingToHome();
+        mSplitSelectStateController.getSplitAnimationController()
+                .playPlaceholderDismissAnim(this);
+    }
+
     public <T extends OverviewActionsView> T getActionsView() {
         return (T) mActionsView;
     }
@@ -1311,9 +1328,9 @@
                                 : groupTask.mSplitBounds.leftTaskPercent);
     }
 
-    public boolean isCommandQueueEmpty() {
+    public boolean canStartHomeSafely() {
         OverviewCommandHelper overviewCommandHelper = mTISBindHelper.getOverviewCommandHelper();
-        return overviewCommandHelper == null || overviewCommandHelper.isCommandQueueEmpty();
+        return overviewCommandHelper == null || overviewCommandHelper.canStartHomeSafely();
     }
 
     private static final class LauncherTaskViewController extends
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
index be53220..b266bcd 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
@@ -30,6 +30,7 @@
 import static com.android.launcher3.util.NavigationMode.THREE_BUTTONS;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 
+import android.animation.AnimatorSet;
 import android.animation.ValueAnimator;
 import android.view.MotionEvent;
 import android.view.animation.Interpolator;
@@ -43,6 +44,7 @@
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.touch.SingleAxisSwipeDetector;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.TouchController;
@@ -51,6 +53,8 @@
 import com.android.quickstep.util.OverviewToHomeAnim;
 import com.android.quickstep.views.RecentsView;
 
+import java.util.function.Consumer;
+
 /**
  * Handles swiping up on the nav bar to go home from launcher, e.g. overview or all apps.
  */
@@ -62,6 +66,7 @@
     private static final float OVERVIEW_TO_HOME_SCRIM_MULTIPLIER = 0.5f;
 
     private final Launcher mLauncher;
+    private final Consumer<AnimatorSet> mCancelSplitRunnable;
     private final SingleAxisSwipeDetector mSwipeDetector;
     private final float mPullbackDistance;
 
@@ -70,8 +75,14 @@
     private LauncherState mEndState = NORMAL;
     private AnimatorPlaybackController mCurrentAnimation;
 
-    public NavBarToHomeTouchController(Launcher launcher) {
+    /**
+     * @param cancelSplitRunnable Called when split placeholder view needs to be cancelled.
+     *                            Animation should be added to the provided AnimatorSet
+     */
+    public NavBarToHomeTouchController(Launcher launcher,
+            Consumer<AnimatorSet> cancelSplitRunnable) {
         mLauncher = launcher;
+        mCancelSplitRunnable = cancelSplitRunnable;
         mSwipeDetector = new SingleAxisSwipeDetector(mLauncher, this,
                 SingleAxisSwipeDetector.VERTICAL);
         mPullbackDistance = mLauncher.getResources().getDimension(R.dimen.home_pullback_distance);
@@ -183,7 +194,10 @@
             recentsView.switchToScreenshot(null,
                     () -> recentsView.finishRecentsAnimation(true /* toRecents */, null));
             if (mStartState.overviewUi) {
-                new OverviewToHomeAnim(mLauncher, () -> onSwipeInteractionCompleted(mEndState))
+                new OverviewToHomeAnim(mLauncher, () -> onSwipeInteractionCompleted(mEndState),
+                        FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()
+                                ? mCancelSplitRunnable
+                                : null)
                         .animateWithVelocity(velocity);
             } else {
                 mLauncher.getStateManager().goToState(mEndState, true,
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
index 2f5467e..4075388 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
@@ -31,6 +31,7 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ONE_HANDED_ACTIVE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
 
+import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.graphics.PointF;
@@ -53,6 +54,8 @@
 import com.android.quickstep.util.OverviewToHomeAnim;
 import com.android.quickstep.views.RecentsView;
 
+import java.util.function.Consumer;
+
 /**
  * Touch controller which handles swipe and hold from the nav bar to go to Overview. Swiping above
  * the nav bar falls back to go to All Apps. Swiping from the nav bar without holding goes to the
@@ -67,6 +70,7 @@
     private static final float TRANSLATION_ANIM_VELOCITY_DP_PER_MS = 0.8f;
 
     private final VibratorWrapper mVibratorWrapper;
+    private final Consumer<AnimatorSet> mCancelSplitRunnable;
     private final RecentsView mRecentsView;
     private final MotionPauseDetector mMotionPauseDetector;
     private final float mMotionPauseMinDisplacement;
@@ -82,12 +86,18 @@
     // Normal to Hint animation has flag SKIP_OVERVIEW, so we update this scrim with this animator.
     private ObjectAnimator mNormalToHintOverviewScrimAnimator;
 
-    public NoButtonNavbarToOverviewTouchController(Launcher l) {
+    /**
+     * @param cancelSplitRunnable Called when split placeholder view needs to be cancelled.
+     *                            Animation should be added to the provided AnimatorSet
+     */
+    public NoButtonNavbarToOverviewTouchController(Launcher l,
+            Consumer<AnimatorSet> cancelSplitRunnable) {
         super(l);
         mRecentsView = l.getOverviewPanel();
         mMotionPauseDetector = new MotionPauseDetector(l);
         mMotionPauseMinDisplacement = ViewConfiguration.get(l).getScaledTouchSlop();
         mVibratorWrapper = VibratorWrapper.INSTANCE.get(l.getApplicationContext());
+        mCancelSplitRunnable = cancelSplitRunnable;
     }
 
     @Override
@@ -197,6 +207,9 @@
             // state, but since the hint state tracks the entire screen without a clear endpoint, we
             // need to manually set the duration to a reasonable value.
             animator.setDuration(HINT_STATE.getTransitionDuration(mLauncher, true /* isToState */));
+            AnimatorSet animatorSet = new AnimatorSet();
+            mCancelSplitRunnable.accept(animatorSet);
+            animatorSet.start();
         }
         if (FeatureFlags.ENABLE_PREMIUM_HAPTICS_ALL_APPS.get() &&
                 ((mFromState == NORMAL && mToState == ALL_APPS)
@@ -268,7 +281,7 @@
     private void goToOverviewOrHomeOnDragEnd(float velocity) {
         boolean goToHomeInsteadOfOverview = !mMotionPauseDetector.isPaused();
         if (goToHomeInsteadOfOverview) {
-            new OverviewToHomeAnim(mLauncher, () -> onSwipeInteractionCompleted(NORMAL))
+            new OverviewToHomeAnim(mLauncher, () -> onSwipeInteractionCompleted(NORMAL), null)
                     .animateWithVelocity(velocity);
         }
         if (mReachedOverview) {
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 95672f3..25909ac 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -107,8 +107,6 @@
 import com.android.launcher3.statemanager.BaseState;
 import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.taskbar.TaskbarUIController;
-import com.android.launcher3.tracing.InputConsumerProto;
-import com.android.launcher3.tracing.SwipeHandlerProto;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
 import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter;
 import com.android.launcher3.util.DisplayController;
@@ -126,7 +124,6 @@
 import com.android.quickstep.util.InputConsumerProxy;
 import com.android.quickstep.util.InputProxyHandlerFactory;
 import com.android.quickstep.util.MotionPauseDetector;
-import com.android.quickstep.util.ProtoTracer;
 import com.android.quickstep.util.RecentsOrientedState;
 import com.android.quickstep.util.RectFSpringAnim;
 import com.android.quickstep.util.StaggeredWorkspaceAnim;
@@ -2426,7 +2423,6 @@
                 taskViewSimulator.apply(remoteHandle.getTransformParams());
             }
         }
-        ProtoTracer.INSTANCE.get(mContext).scheduleFrameUpdate();
     }
 
     // Scaling of RecentsView during quick switch based on amount of recents scroll
@@ -2496,26 +2492,6 @@
                 mRecentsAnimationTargets.nonApps, shown, null /* animatorHandler */);
     }
 
-    /**
-     * Used for winscope tracing, see launcher_trace.proto
-     * @see com.android.systemui.shared.tracing.ProtoTraceable#writeToProto
-     * @param inputConsumerProto The parent of this proto message.
-     */
-    public void writeToProto(InputConsumerProto.Builder inputConsumerProto) {
-        SwipeHandlerProto.Builder swipeHandlerProto = SwipeHandlerProto.newBuilder();
-
-        mGestureState.writeToProto(swipeHandlerProto);
-
-        swipeHandlerProto.setIsRecentsAttachedToAppWindow(
-                mAnimationFactory.isRecentsAttachedToAppWindow());
-        swipeHandlerProto.setScrollOffset(mRecentsView == null
-                ? 0
-                : mRecentsView.getScrollOffset());
-        swipeHandlerProto.setAppToOverviewProgress(mCurrentShift.value);
-
-        inputConsumerProto.setSwipeHandler(swipeHandlerProto);
-    }
-
     public interface Factory {
         AbsSwipeUpHandler newHandler(GestureState gestureState, long touchTimeMs);
     }
diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java
index 3d0f6d5..c7df18f 100644
--- a/quickstep/src/com/android/quickstep/GestureState.java
+++ b/quickstep/src/com/android/quickstep/GestureState.java
@@ -37,8 +37,6 @@
 
 import com.android.launcher3.statemanager.BaseState;
 import com.android.launcher3.statemanager.StatefulActivity;
-import com.android.launcher3.tracing.GestureStateProto;
-import com.android.launcher3.tracing.SwipeHandlerProto;
 import com.android.quickstep.TopTaskTracker.CachedTaskInfo;
 import com.android.quickstep.util.ActiveGestureErrorDetector;
 import com.android.quickstep.util.ActiveGestureLog;
@@ -62,24 +60,21 @@
      * Defines the end targets of a gesture and the associated state.
      */
     public enum GestureEndTarget {
-        HOME(true, LAUNCHER_STATE_HOME, false, GestureStateProto.GestureEndTarget.HOME),
+        HOME(true, LAUNCHER_STATE_HOME, false),
 
-        RECENTS(true, LAUNCHER_STATE_OVERVIEW, true, GestureStateProto.GestureEndTarget.RECENTS),
+        RECENTS(true, LAUNCHER_STATE_OVERVIEW, true),
 
-        NEW_TASK(false, LAUNCHER_STATE_BACKGROUND, true,
-                GestureStateProto.GestureEndTarget.NEW_TASK),
+        NEW_TASK(false, LAUNCHER_STATE_BACKGROUND, true),
 
-        LAST_TASK(false, LAUNCHER_STATE_BACKGROUND, true,
-                GestureStateProto.GestureEndTarget.LAST_TASK),
+        LAST_TASK(false, LAUNCHER_STATE_BACKGROUND, true),
 
-        ALL_APPS(true, LAUNCHER_STATE_ALLAPPS, false, GestureStateProto.GestureEndTarget.ALL_APPS);
+        ALL_APPS(true, LAUNCHER_STATE_ALLAPPS, false);
 
-        GestureEndTarget(boolean isLauncher, int containerType, boolean recentsAttachedToAppWindow,
-                GestureStateProto.GestureEndTarget protoEndTarget) {
+        GestureEndTarget(boolean isLauncher, int containerType,
+                boolean recentsAttachedToAppWindow) {
             this.isLauncher = isLauncher;
             this.containerType = containerType;
             this.recentsAttachedToAppWindow = recentsAttachedToAppWindow;
-            this.protoEndTarget = protoEndTarget;
         }
 
         /** Whether the target is in the launcher activity. Implicitly, if the end target is going
@@ -89,8 +84,6 @@
         public final int containerType;
         /** Whether RecentsView should be attached to the window as we animate to this target */
         public final boolean recentsAttachedToAppWindow;
-        /** The GestureStateProto enum value, used for winscope tracing. See launcher_trace.proto */
-        public final GestureStateProto.GestureEndTarget protoEndTarget;
     }
 
     private static final String TAG = "GestureState";
@@ -489,17 +482,4 @@
         pw.println("  lastStartedTaskId=" + mLastStartedTaskId);
         pw.println("  isRecentsAnimationRunning=" + isRecentsAnimationRunning());
     }
-
-    /**
-     * Used for winscope tracing, see launcher_trace.proto
-     * @see com.android.systemui.shared.tracing.ProtoTraceable#writeToProto
-     * @param swipeHandlerProto The parent of this proto message.
-     */
-    public void writeToProto(SwipeHandlerProto.Builder swipeHandlerProto) {
-        GestureStateProto.Builder gestureStateProto = GestureStateProto.newBuilder();
-        gestureStateProto.setEndTarget(mEndTarget == null
-                ? GestureStateProto.GestureEndTarget.UNSET
-                : mEndTarget.protoEndTarget);
-        swipeHandlerProto.setGestureState(gestureStateProto);
-    }
 }
diff --git a/quickstep/src/com/android/quickstep/InputConsumer.java b/quickstep/src/com/android/quickstep/InputConsumer.java
index 2071103..23def14 100644
--- a/quickstep/src/com/android/quickstep/InputConsumer.java
+++ b/quickstep/src/com/android/quickstep/InputConsumer.java
@@ -21,9 +21,6 @@
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 
-import com.android.launcher3.tracing.InputConsumerProto;
-import com.android.launcher3.tracing.TouchInteractionServiceProto;
-
 @TargetApi(Build.VERSION_CODES.O)
 public interface InputConsumer {
 
@@ -129,21 +126,4 @@
         }
         return name.toString();
     }
-
-    /**
-     * Used for winscope tracing, see launcher_trace.proto
-     * @see com.android.systemui.shared.tracing.ProtoTraceable#writeToProto
-     * @param serviceProto The parent of this proto message.
-     */
-    default void writeToProto(TouchInteractionServiceProto.Builder serviceProto) {
-        InputConsumerProto.Builder inputConsumerProto = InputConsumerProto.newBuilder();
-        inputConsumerProto.setName(getName());
-        writeToProtoInternal(inputConsumerProto);
-        serviceProto.setInputConsumer(inputConsumerProto);
-    }
-
-    /**
-     * @see #writeToProto - allows subclasses to write additional info to the proto.
-     */
-    default void writeToProtoInternal(InputConsumerProto.Builder inputConsumerProto) {}
 }
diff --git a/quickstep/src/com/android/quickstep/InstantAppResolverImpl.java b/quickstep/src/com/android/quickstep/InstantAppResolverImpl.java
index 529213c..33a2366 100644
--- a/quickstep/src/com/android/quickstep/InstantAppResolverImpl.java
+++ b/quickstep/src/com/android/quickstep/InstantAppResolverImpl.java
@@ -16,12 +16,12 @@
 
 package com.android.quickstep;
 
-import android.app.ActivityThread;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
-import android.os.RemoteException;
+import android.os.Process;
+import android.os.UserHandle;
 import android.util.Log;
 
 import com.android.launcher3.model.data.AppInfo;
@@ -55,9 +55,13 @@
 
     @Override
     public boolean isInstantApp(String packageName, int userId) {
+        if (!Process.myUserHandle().equals(UserHandle.of(userId))) {
+            // Instant app can only exist on current user
+            return false;
+        }
         try {
-            return ActivityThread.getPackageManager().isInstantApp(packageName, userId);
-        } catch (RemoteException e) {
+            return mPM.isInstantApp(packageName);
+        } catch (Exception e) {
             Log.e(TAG, "Failed to determine whether package is instant app " + packageName, e);
             return false;
         }
diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index 13da40a..5784c37 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -18,6 +18,7 @@
 import static com.android.app.animation.Interpolators.LINEAR;
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
+import static com.android.launcher3.LauncherState.FLOATING_SEARCH_BAR;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
@@ -267,7 +268,9 @@
 
     @Override
     public boolean allowAllAppsFromOverview() {
-        return FeatureFlags.ENABLE_ALL_APPS_FROM_OVERVIEW.get();
+        return FeatureFlags.ENABLE_ALL_APPS_FROM_OVERVIEW.get()
+                // If floating search bar would not show in overview, don't allow all apps gesture.
+                && OVERVIEW.areElementsVisible(getCreatedActivity(), FLOATING_SEARCH_BAR);
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index 4a60566..42bf1ac 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -141,8 +141,8 @@
     }
 
     @UiThread
-    public boolean isCommandQueueEmpty() {
-        return mPendingCommands.isEmpty();
+    public boolean canStartHomeSafely() {
+        return mPendingCommands.isEmpty() || mPendingCommands.get(0).type == TYPE_HOME;
     }
 
     @Nullable
diff --git a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
index a8f3c3a..60713cf 100644
--- a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
+++ b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
@@ -38,8 +38,6 @@
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.R;
-import com.android.launcher3.tracing.OverviewComponentObserverProto;
-import com.android.launcher3.tracing.TouchInteractionServiceProto;
 import com.android.launcher3.util.SimpleBroadcastReceiver;
 import com.android.systemui.shared.system.PackageManagerWrapper;
 
@@ -276,19 +274,6 @@
     }
 
     /**
-     * Used for winscope tracing, see launcher_trace.proto
-     * @see com.android.systemui.shared.tracing.ProtoTraceable#writeToProto
-     * @param serviceProto The parent of this proto message.
-     */
-    public void writeToProto(TouchInteractionServiceProto.Builder serviceProto) {
-        OverviewComponentObserverProto.Builder overviewComponentObserver =
-                OverviewComponentObserverProto.newBuilder();
-        overviewComponentObserver.setOverviewActivityStarted(mActivityInterface.isStarted());
-        overviewComponentObserver.setOverviewActivityResumed(mActivityInterface.isResumed());
-        serviceProto.setOverviewComponentObvserver(overviewComponentObserver);
-    }
-
-    /**
      * Starts the intent for the current home activity.
      */
     public static void startHomeIntentSafely(@NonNull Context context, @Nullable Bundle options) {
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index 7cb6eb6..33a8261 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -58,6 +58,7 @@
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.statemanager.StateManager;
 import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory;
@@ -117,6 +118,7 @@
     // animation callback
     private final Handler mHandler = new Handler();
     private final Runnable mAnimationStartTimeoutRunnable = this::onAnimationStartTimeout;
+    private SplitSelectStateController mSplitSelectStateController;
 
     /**
      * Init drag layer and overview panel views.
@@ -129,13 +131,12 @@
         mFallbackRecentsView = findViewById(R.id.overview_panel);
         mActionsView = findViewById(R.id.overview_actions_view);
         getRootView().getSysUiScrim().getSysUIProgress().updateValue(0);
-
-        SplitSelectStateController controller =
+        mSplitSelectStateController =
                 new SplitSelectStateController(this, mHandler, getStateManager(),
-                         null /* depthController */, getStatsLogManager(),
+                        null /* depthController */, getStatsLogManager(),
                         SystemUiProxy.INSTANCE.get(this), RecentsModel.INSTANCE.get(this));
         mDragLayer.recreateControllers();
-        mFallbackRecentsView.init(mActionsView, controller);
+        mFallbackRecentsView.init(mActionsView, mSplitSelectStateController);
 
         mTISBindHelper = new TISBindHelper(this, this::onTISConnected);
     }
@@ -469,8 +470,8 @@
         };
     }
 
-    public boolean isCommandQueueEmpty() {
+    public boolean canStartHomeSafely() {
         OverviewCommandHelper overviewCommandHelper = mTISBindHelper.getOverviewCommandHelper();
-        return overviewCommandHelper == null || overviewCommandHelper.isCommandQueueEmpty();
+        return overviewCommandHelper == null || overviewCommandHelper.canStartHomeSafely();
     }
 }
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
index 523a98e..2256cbf 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
@@ -15,6 +15,7 @@
  */
 package com.android.quickstep;
 
+import static android.view.RemoteAnimationTarget.MODE_CLOSING;
 import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
 
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
@@ -37,6 +38,7 @@
 import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Set;
 
@@ -101,9 +103,22 @@
             RemoteAnimationTarget[] appTargets,
             RemoteAnimationTarget[] wallpaperTargets,
             Rect homeContentInsets, Rect minimizedHomeBounds) {
+        long appCount = Arrays.stream(appTargets)
+                .filter(app -> app.mode == MODE_CLOSING)
+                .count();
+        if (appCount == 0) {
+            // Edge case, if there are no closing app targets, then Launcher has nothing to handle
+            ActiveGestureLog.INSTANCE.addLog(
+                    /* event= */ "RecentsAnimationCallbacks.onAnimationStart (canceled)",
+                    /* extras= */ 0,
+                    /* gestureEvent= */ START_RECENTS_ANIMATION);
+            notifyAnimationCanceled();
+            animationController.finish(false /* toHome */, false /* sendUserLeaveHint */);
+            return;
+        }
+
         mController = new RecentsAnimationController(animationController,
                 mAllowMinimizeSplitScreen, this::onAnimationFinished);
-
         if (mCancelled) {
             Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(),
                     mController::finishAnimationToApp);
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index 7693e69..1448a52 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -239,7 +239,7 @@
     public void onDisplayInfoChanged(Context context, Info info, int flags) {
         if ((flags & (CHANGE_ROTATION | CHANGE_NAVIGATION_MODE)) != 0) {
             mMode = info.navigationMode;
-            mNavBarPosition = new NavBarPosition(mContext, mMode, info);
+            mNavBarPosition = new NavBarPosition(mMode, info);
 
             if (mMode == NO_BUTTON) {
                 mExclusionListener.register();
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index dd6499b..60784f5 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -19,6 +19,7 @@
 
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+import static com.android.quickstep.util.LogUtils.splitFailureMessage;
 
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
@@ -645,13 +646,15 @@
     /**
      * Tells SysUI to show the bubble with the provided key.
      * @param key the key of the bubble to show.
-     * @param onLauncherHome whether the bubble is showing on launcher home or not (modifies where
-     *                       the expanded bubble view is placed).
+     * @param bubbleBarOffsetX the offset of the bubble bar from the edge of the screen on the X
+     *                         axis.
+     * @param bubbleBarOffsetY the offset of the bubble bar from the edge of the screen on the Y
+     *                         axis.
      */
-    public void showBubble(String key, boolean onLauncherHome) {
+    public void showBubble(String key, int bubbleBarOffsetX, int bubbleBarOffsetY) {
         if (mBubbles != null) {
             try {
-                mBubbles.showBubble(key, onLauncherHome);
+                mBubbles.showBubble(key, bubbleBarOffsetX, bubbleBarOffsetY);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed call showBubble");
             }
@@ -706,7 +709,7 @@
                 mSplitScreen.startTasks(taskId1, options1, taskId2, options2, splitPosition,
                         splitRatio, remoteTransition, instanceId);
             } catch (RemoteException e) {
-                Log.w(TAG, "Failed call startTasks");
+                Log.w(TAG, splitFailureMessage("startTasks", "RemoteException"), e);
             }
         }
     }
@@ -719,7 +722,7 @@
                 mSplitScreen.startIntentAndTask(pendingIntent, userId1, options1, taskId, options2,
                         splitPosition, splitRatio, remoteTransition, instanceId);
             } catch (RemoteException e) {
-                Log.w(TAG, "Failed call startIntentAndTask");
+                Log.w(TAG, splitFailureMessage("startIntentAndTask", "RemoteException"), e);
             }
         }
     }
@@ -735,7 +738,7 @@
                         pendingIntent2, userId2, shortcutInfo2, options2, splitPosition, splitRatio,
                         remoteTransition, instanceId);
             } catch (RemoteException e) {
-                Log.w(TAG, "Failed call startIntents");
+                Log.w(TAG, splitFailureMessage("startIntents", "RemoteException"), e);
             }
         }
     }
@@ -748,7 +751,7 @@
                 mSplitScreen.startShortcutAndTask(shortcutInfo, options1, taskId, options2,
                         splitPosition, splitRatio, remoteTransition, instanceId);
             } catch (RemoteException e) {
-                Log.w(TAG, "Failed call startShortcutAndTask");
+                Log.w(TAG, splitFailureMessage("startShortcutAndTask", "RemoteException"), e);
             }
         }
     }
@@ -764,7 +767,8 @@
                 mSplitScreen.startTasksWithLegacyTransition(taskId1, options1, taskId2, options2,
                         splitPosition, splitRatio, adapter, instanceId);
             } catch (RemoteException e) {
-                Log.w(TAG, "Failed call startTasksWithLegacyTransition");
+                Log.w(TAG, splitFailureMessage(
+                        "startTasksWithLegacyTransition", "RemoteException"), e);
             }
         }
     }
@@ -778,7 +782,8 @@
                 mSplitScreen.startIntentAndTaskWithLegacyTransition(pendingIntent, userId1,
                         options1, taskId, options2, splitPosition, splitRatio, adapter, instanceId);
             } catch (RemoteException e) {
-                Log.w(TAG, "Failed call startIntentAndTaskWithLegacyTransition");
+                Log.w(TAG, splitFailureMessage(
+                        "startIntentAndTaskWithLegacyTransition", "RemoteException"), e);
             }
         }
     }
@@ -791,7 +796,8 @@
                 mSplitScreen.startShortcutAndTaskWithLegacyTransition(shortcutInfo, options1,
                         taskId, options2, splitPosition, splitRatio, adapter, instanceId);
             } catch (RemoteException e) {
-                Log.w(TAG, "Failed call startShortcutAndTaskWithLegacyTransition");
+                Log.w(TAG, splitFailureMessage(
+                        "startShortcutAndTaskWithLegacyTransition", "RemoteException"), e);
             }
         }
     }
@@ -811,7 +817,8 @@
                         shortcutInfo1, options1, pendingIntent2, userId2, shortcutInfo2, options2,
                         sidePosition, splitRatio, adapter, instanceId);
             } catch (RemoteException e) {
-                Log.w(TAG, "Failed call startIntentsWithLegacyTransition");
+                Log.w(TAG, splitFailureMessage(
+                        "startIntentsWithLegacyTransition", "RemoteException"), e);
             }
         }
     }
@@ -823,7 +830,7 @@
                 mSplitScreen.startShortcut(packageName, shortcutId, position, options,
                         user, instanceId);
             } catch (RemoteException e) {
-                Log.w(TAG, "Failed call startShortcut");
+                Log.w(TAG, splitFailureMessage("startShortcut", "RemoteException"), e);
             }
         }
     }
@@ -835,7 +842,7 @@
                 mSplitScreen.startIntent(intent, userId, fillInIntent, position, options,
                         instanceId);
             } catch (RemoteException e) {
-                Log.w(TAG, "Failed call startIntent");
+                Log.w(TAG, splitFailureMessage("startIntent", "RemoteException"), e);
             }
         }
     }
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index b5c0f7d..a1ecaeb 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -92,8 +92,6 @@
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.shared.ResourceUtils;
 import com.android.launcher3.testing.shared.TestProtocol;
-import com.android.launcher3.tracing.LauncherTraceProto;
-import com.android.launcher3.tracing.TouchInteractionServiceProto;
 import com.android.launcher3.uioverrides.flags.FlagsFactory;
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
 import com.android.launcher3.util.DisplayController;
@@ -117,7 +115,6 @@
 import com.android.quickstep.inputconsumers.TrackpadStatusBarInputConsumer;
 import com.android.quickstep.util.ActiveGestureLog;
 import com.android.quickstep.util.ActiveGestureLog.CompoundString;
-import com.android.quickstep.util.ProtoTracer;
 import com.android.quickstep.util.ProxyScreenStatusProvider;
 import com.android.systemui.shared.recents.IOverviewProxy;
 import com.android.systemui.shared.recents.ISystemUiProxy;
@@ -126,7 +123,6 @@
 import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.shared.system.InputMonitorCompat;
 import com.android.systemui.shared.system.smartspace.ISysuiUnlockAnimationController;
-import com.android.systemui.shared.tracing.ProtoTraceable;
 import com.android.systemui.unfold.progress.IUnfoldAnimation;
 import com.android.wm.shell.back.IBackAnimation;
 import com.android.wm.shell.bubbles.IBubbles;
@@ -149,8 +145,7 @@
  * Service connected by system-UI for handling touch interaction.
  */
 @TargetApi(Build.VERSION_CODES.R)
-public class TouchInteractionService extends Service
-        implements ProtoTraceable<LauncherTraceProto.Builder> {
+public class TouchInteractionService extends Service {
 
     private static final String SUBSTRING_PREFIX = "; ";
     private static final String NEWLINE_PREFIX = "\n\t\t\t-> ";
@@ -491,8 +486,6 @@
         LockedUserState.get(this).runOnUserUnlocked(this::onUserUnlocked);
         LockedUserState.get(this).runOnUserUnlocked(mTaskbarManager::onUserUnlocked);
         mDeviceState.addNavigationModeChangedCallback(this::onNavigationModeChanged);
-
-        ProtoTracer.INSTANCE.get(this).add(this);
         sConnected = true;
     }
 
@@ -617,19 +610,6 @@
                 // overview.
                 mTaskAnimationManager.endLiveTile();
             }
-
-            if ((lastSysUIFlags & SYSUI_STATE_TRACING_ENABLED) !=
-                    (systemUiStateFlags & SYSUI_STATE_TRACING_ENABLED)) {
-                // Update the tracing state
-                if ((systemUiStateFlags & SYSUI_STATE_TRACING_ENABLED) != 0) {
-                    Log.d(TAG, "Starting tracing.");
-                    ProtoTracer.INSTANCE.get(this).start();
-                } else {
-                    Log.d(TAG, "Stopping tracing. Dumping to file="
-                            + ProtoTracer.INSTANCE.get(this).getTraceFile());
-                    ProtoTracer.INSTANCE.get(this).stop();
-                }
-            }
         }
     }
 
@@ -652,8 +632,6 @@
         disposeEventHandlers("TouchInteractionService onDestroy()");
         mDeviceState.destroy();
         SystemUiProxy.INSTANCE.get(this).clearProxy();
-        ProtoTracer.INSTANCE.get(this).stop();
-        ProtoTracer.INSTANCE.get(this).remove(this);
 
         getSystemService(AccessibilityManager.class)
                 .unregisterSystemAction(GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS);
@@ -781,7 +759,6 @@
             reset();
         }
         traceToken.close();
-        ProtoTracer.INSTANCE.get(this).scheduleFrameUpdate();
     }
 
     private InputConsumer tryCreateAssistantInputConsumer(
@@ -1302,8 +1279,6 @@
         pw.println("  mConsumer=" + mConsumer.getName());
         ActiveGestureLog.INSTANCE.dump("", pw);
         RecentsModel.INSTANCE.get(this).dump("", pw);
-        pw.println("ProtoTrace:");
-        pw.println("  file=" + ProtoTracer.INSTANCE.get(this).getTraceFile());
         if (createdOverviewActivity != null) {
             createdOverviewActivity.getDeviceProfile().dump(this, "", pw);
         }
@@ -1323,18 +1298,4 @@
                 gestureState, touchTimeMs, mTaskAnimationManager.isRecentsAnimationRunning(),
                 mInputConsumer);
     }
-
-    @Override
-    public void writeToProto(LauncherTraceProto.Builder proto) {
-        TouchInteractionServiceProto.Builder serviceProto =
-            TouchInteractionServiceProto.newBuilder();
-        serviceProto.setServiceConnected(true);
-
-        if (mOverviewComponentObserver != null) {
-            mOverviewComponentObserver.writeToProto(serviceProto);
-        }
-        mConsumer.writeToProto(serviceProto);
-
-        proto.setTouchInteractionService(serviceProto);
-    }
 }
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackNavBarTouchController.java b/quickstep/src/com/android/quickstep/fallback/FallbackNavBarTouchController.java
index 66f5c00..8a87f63 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackNavBarTouchController.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackNavBarTouchController.java
@@ -42,7 +42,7 @@
         mActivity = activity;
         NavigationMode sysUINavigationMode = DisplayController.getNavigationMode(mActivity);
         if (sysUINavigationMode == NavigationMode.NO_BUTTON) {
-            NavBarPosition navBarPosition = new NavBarPosition(mActivity, sysUINavigationMode,
+            NavBarPosition navBarPosition = new NavBarPosition(sysUINavigationMode,
                     DisplayController.INSTANCE.get(mActivity).getInfo());
             mTriggerSwipeUpTracker = new TriggerSwipeUpTouchTracker(mActivity,
                     true /* disableHorizontalSwipe */, navBarPosition,
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index 7dc8347..95d88cd 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -86,8 +86,8 @@
     }
 
     @Override
-    protected boolean isCommandQueueEmpty() {
-        return mActivity.isCommandQueueEmpty();
+    protected boolean canStartHomeSafely() {
+        return mActivity.canStartHomeSafely();
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
index 03f8eef..858999e 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
@@ -4,7 +4,6 @@
 
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.shared.TestProtocol;
-import com.android.launcher3.tracing.InputConsumerProto;
 import com.android.quickstep.InputConsumer;
 import com.android.systemui.shared.system.InputMonitorCompat;
 
@@ -54,9 +53,4 @@
         mDelegate.onMotionEvent(event);
         event.recycle();
     }
-
-    @Override
-    public void writeToProtoInternal(InputConsumerProto.Builder inputConsumerProto) {
-        mDelegate.writeToProtoInternal(inputConsumerProto);
-    }
 }
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index 10c6316..2816228 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -48,7 +48,6 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.shared.TestProtocol;
-import com.android.launcher3.tracing.InputConsumerProto;
 import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.TraceHelper;
 import com.android.quickstep.AbsSwipeUpHandler;
@@ -509,13 +508,6 @@
         return !mPassedPilferInputSlop;
     }
 
-    @Override
-    public void writeToProtoInternal(InputConsumerProto.Builder inputConsumerProto) {
-        if (mInteractionHandler != null) {
-            mInteractionHandler.writeToProto(inputConsumerProto);
-        }
-    }
-
     /**
      * A listener which just finishes the animation immediately after starting. Replaces
      * AbsSwipeUpHandler if the gesture itself finishes before the animation even starts.
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/ProgressDelegateInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/ProgressDelegateInputConsumer.java
index 5202529..c9c64b6 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/ProgressDelegateInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/ProgressDelegateInputConsumer.java
@@ -103,7 +103,8 @@
         mStateCallback = new MultiStateCallback(STATE_NAMES);
         mStateCallback.runOnceAtState(STATE_TARGET_RECEIVED | STATE_HANDLER_INVALIDATED,
                 this::endRemoteAnimation);
-        mStateCallback.runOnceAtState(STATE_FLING_FINISHED, this::onFlingFinished);
+        mStateCallback.runOnceAtState(STATE_TARGET_RECEIVED | STATE_FLING_FINISHED,
+                this::onFlingFinished);
 
         mSwipeDetector = new SingleAxisSwipeDetector(mContext, this, VERTICAL);
         mSwipeDetector.setDetectableScrollConditions(DIRECTION_POSITIVE, false);
diff --git a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
index 42b0edf..9f6119c 100644
--- a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
+++ b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
@@ -252,6 +252,10 @@
         binder.setSwipeUpProxy(isResumed() ? this::createSwipeUpProxy : null);
         binder.setOverviewTargetChangeListener(binder::preloadOverviewForSUWAllSet);
         binder.preloadOverviewForSUWAllSet();
+        TaskbarManager taskbarManager = binder.getTaskbarManager();
+        if (taskbarManager != null) {
+            mLauncherStartAnim = taskbarManager.createLauncherStartFromSuwAnim(MAX_SWIPE_DURATION);
+        }
     }
 
     @Override
@@ -327,13 +331,9 @@
         mRootView.setAlpha(alpha);
         mRootView.setTranslationY((alpha - 1) * mSwipeUpShift);
 
-        TaskbarManager taskbarManager = mTISBindHelper.getTaskbarManager();
-        if (mLauncherStartAnim == null && taskbarManager != null) {
-            mLauncherStartAnim = taskbarManager.createLauncherStartFromSuwAnim(MAX_SWIPE_DURATION);
-        }
         if (mLauncherStartAnim != null) {
-            mLauncherStartAnim.setPlayFraction(Utilities.mapBoundToRange(
-                    mSwipeProgress.value, 0, 1, 0, 1, FAST_OUT_SLOW_IN));
+            mLauncherStartAnim.setPlayFraction(
+                    FAST_OUT_SLOW_IN.getInterpolation(mSwipeProgress.value));
         }
         maybeResumeOrPauseBackgroundAnimation();
     }
diff --git a/quickstep/src/com/android/quickstep/interaction/MenuFragment.java b/quickstep/src/com/android/quickstep/interaction/MenuFragment.java
index 46f79b1..c19d44a 100644
--- a/quickstep/src/com/android/quickstep/interaction/MenuFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/MenuFragment.java
@@ -19,7 +19,6 @@
 import static com.android.quickstep.interaction.GestureSandboxActivity.KEY_TUTORIAL_TYPE;
 import static com.android.quickstep.interaction.GestureSandboxActivity.KEY_USE_TUTORIAL_MENU;
 
-import android.graphics.Rect;
 import android.os.Bundle;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -28,33 +27,17 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
-import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.R;
 
 /** Displays the gesture nav tutorial menu. */
 public final class MenuFragment extends GestureSandboxFragment {
 
-    @NonNull private Rect mInsets = new Rect();
-
-    @Override
-    public void onCreate(@Nullable Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        mInsets = InvariantDeviceProfile.INSTANCE.get(getContext())
-                .getDeviceProfile(getContext()).getInsets();
-    }
-
     @Override
     public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
             @Nullable Bundle savedInstanceState) {
-        View root = inflater.inflate(
+        final View root = inflater.inflate(
                 R.layout.gesture_tutorial_step_menu, container, false);
 
-        root.setPadding(
-                root.getPaddingLeft() + mInsets.left,
-                root.getPaddingTop() + mInsets.top,
-                root.getPaddingRight() + mInsets.right,
-                root.getPaddingBottom() + mInsets.bottom);
-
         root.findViewById(R.id.gesture_tutorial_menu_home_button).setOnClickListener(
                 v -> launchTutorialStep(TutorialController.TutorialType.HOME_NAVIGATION));
         root.findViewById(R.id.gesture_tutorial_menu_back_button).setOnClickListener(
diff --git a/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
index 63e41d5..c4a2216 100644
--- a/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
+++ b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
@@ -60,12 +60,11 @@
     NavBarGestureHandler(Context context) {
         mContext = context;
         DisplayController.Info displayInfo = DisplayController.INSTANCE.get(mContext).getInfo();
-        final int displayRotation = displayInfo.rotation;
         Point currentSize = displayInfo.currentSize;
         mDisplaySize.set(currentSize.x, currentSize.y);
         mSwipeUpTouchTracker =
                 new TriggerSwipeUpTouchTracker(context, true /*disableHorizontalSwipe*/,
-                        new NavBarPosition(mContext, NavigationMode.NO_BUTTON, displayRotation),
+                        new NavBarPosition(NavigationMode.NO_BUTTON, displayInfo),
                         null /*onInterceptTouch*/, this);
         mMotionPauseDetector = new MotionPauseDetector(context);
 
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index 6288937..1f06f94 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -110,8 +110,6 @@
     public static final CopyOnWriteArrayList<StatsLogConsumer> LOGS_CONSUMER =
             new CopyOnWriteArrayList<>();
 
-    private final Context mContext;
-
     public StatsLogCompatManager(Context context) {
         mContext = context;
     }
diff --git a/quickstep/src/com/android/quickstep/util/LogUtils.kt b/quickstep/src/com/android/quickstep/util/LogUtils.kt
index 23a41f6..e34c4ec 100644
--- a/quickstep/src/com/android/quickstep/util/LogUtils.kt
+++ b/quickstep/src/com/android/quickstep/util/LogUtils.kt
@@ -20,6 +20,11 @@
 import com.android.launcher3.logging.InstanceId
 
 object LogUtils {
+    @JvmStatic
+    fun splitFailureMessage(caller: String, reason: String): String {
+        return "($caller) Splitscreen aborted: $reason"
+    }
+
     /**
      * @return a [Pair] of two InstanceIds but with different types, one that can be used by
      *   framework (if needing to pass through an intent or such) and one used in Launcher
diff --git a/quickstep/src/com/android/quickstep/util/NavBarPosition.java b/quickstep/src/com/android/quickstep/util/NavBarPosition.java
index a89e7e3..9418512 100644
--- a/quickstep/src/com/android/quickstep/util/NavBarPosition.java
+++ b/quickstep/src/com/android/quickstep/util/NavBarPosition.java
@@ -16,9 +16,7 @@
 package com.android.quickstep.util;
 
 import static com.android.launcher3.util.NavigationMode.NO_BUTTON;
-import static com.android.systemui.shared.recents.utilities.Utilities.isLargeScreen;
 
-import android.content.Context;
 import android.view.Surface;
 
 import com.android.launcher3.util.DisplayController.Info;
@@ -29,26 +27,22 @@
  */
 public class NavBarPosition {
 
-    private final boolean mIsLargeScreen;
+    private final boolean mIsTablet;
     private final NavigationMode mMode;
     private final int mDisplayRotation;
 
-    public NavBarPosition(Context context, NavigationMode mode, Info info) {
-        this(context, mode, info.rotation);
-    }
-
-    public NavBarPosition(Context context, NavigationMode mode, int displayRotation) {
-        mIsLargeScreen = isLargeScreen(context);
+    public NavBarPosition(NavigationMode mode, Info info) {
+        mIsTablet = info.isTablet(info.realBounds);
         mMode = mode;
-        mDisplayRotation = displayRotation;
+        mDisplayRotation = info.rotation;
     }
 
     public boolean isRightEdge() {
-        return mMode != NO_BUTTON && mDisplayRotation == Surface.ROTATION_90 && !mIsLargeScreen;
+        return mMode != NO_BUTTON && mDisplayRotation == Surface.ROTATION_90 && !mIsTablet;
     }
 
     public boolean isLeftEdge() {
-        return mMode != NO_BUTTON && mDisplayRotation == Surface.ROTATION_270 && !mIsLargeScreen;
+        return mMode != NO_BUTTON && mDisplayRotation == Surface.ROTATION_270 && !mIsTablet;
     }
 
     public float getRotation() {
diff --git a/quickstep/src/com/android/quickstep/util/OverviewToHomeAnim.java b/quickstep/src/com/android/quickstep/util/OverviewToHomeAnim.java
index 3cec1a4..6d9ecd9 100644
--- a/quickstep/src/com/android/quickstep/util/OverviewToHomeAnim.java
+++ b/quickstep/src/com/android/quickstep/util/OverviewToHomeAnim.java
@@ -22,12 +22,16 @@
 import android.animation.AnimatorSet;
 import android.util.Log;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.statemanager.StateManager;
 import com.android.launcher3.states.StateAnimationConfig;
 
+import java.util.function.Consumer;
+
 /**
  * Runs an animation from overview to home. Currently, this animation is just a wrapper around the
  * normal state transition and may play a {@link WorkspaceRevealAnim} if we're starting from an
@@ -39,14 +43,18 @@
 
     private final Launcher mLauncher;
     private final Runnable mOnReachedHome;
+    @Nullable
+    private final Consumer<AnimatorSet> mSplitCancelConsumer;
 
     // Only run mOnReachedHome when both of these are true.
     private boolean mIsHomeStaggeredAnimFinished;
     private boolean mIsOverviewHidden;
 
-    public OverviewToHomeAnim(Launcher launcher, Runnable onReachedHome) {
+    public OverviewToHomeAnim(Launcher launcher, Runnable onReachedHome,
+            @Nullable Consumer<AnimatorSet> splitCancelConsumer) {
         mLauncher = launcher;
         mOnReachedHome = onReachedHome;
+        mSplitCancelConsumer = splitCancelConsumer;
     }
 
     /**
@@ -92,6 +100,11 @@
                 maybeOverviewToHomeAnimComplete();
             }
         });
+
+        if (mSplitCancelConsumer != null) {
+            // Clear split state when swiping to home
+            mSplitCancelConsumer.accept(anim);
+        }
         anim.play(stateAnim);
         stateManager.setCurrentAnimation(anim, NORMAL);
         anim.start();
diff --git a/quickstep/src/com/android/quickstep/util/ProtoTracer.java b/quickstep/src/com/android/quickstep/util/ProtoTracer.java
deleted file mode 100644
index ef9586d..0000000
--- a/quickstep/src/com/android/quickstep/util/ProtoTracer.java
+++ /dev/null
@@ -1,135 +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.quickstep.util;
-
-import static com.android.launcher3.tracing.LauncherTraceFileProto.MagicNumber.MAGIC_NUMBER_H_VALUE;
-import static com.android.launcher3.tracing.LauncherTraceFileProto.MagicNumber.MAGIC_NUMBER_L_VALUE;
-
-import android.content.Context;
-import android.os.SystemClock;
-
-import android.os.Trace;
-import com.android.launcher3.tracing.LauncherTraceProto;
-import com.android.launcher3.tracing.LauncherTraceEntryProto;
-import com.android.launcher3.tracing.LauncherTraceFileProto;
-import com.android.launcher3.util.MainThreadInitializedObject;
-import com.android.systemui.shared.tracing.FrameProtoTracer;
-import com.android.systemui.shared.tracing.FrameProtoTracer.ProtoTraceParams;
-import com.android.systemui.shared.tracing.ProtoTraceable;
-import com.google.protobuf.MessageLite;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Queue;
-
-
-/**
- * Controller for coordinating winscope proto tracing.
- */
-public class ProtoTracer implements ProtoTraceParams<MessageLite.Builder,
-        LauncherTraceFileProto.Builder, LauncherTraceEntryProto.Builder,
-                LauncherTraceProto.Builder> {
-
-    public static final MainThreadInitializedObject<ProtoTracer> INSTANCE =
-            new MainThreadInitializedObject<>(ProtoTracer::new);
-
-    private static final String TAG = "ProtoTracer";
-    private static final long MAGIC_NUMBER_VALUE =
-            ((long) MAGIC_NUMBER_H_VALUE << 32) | MAGIC_NUMBER_L_VALUE;
-
-    private final Context mContext;
-    private final FrameProtoTracer<MessageLite.Builder, LauncherTraceFileProto.Builder,
-        LauncherTraceEntryProto.Builder, LauncherTraceProto.Builder> mProtoTracer;
-
-    public ProtoTracer(Context context) {
-        mContext = context;
-        mProtoTracer = new FrameProtoTracer<>(this);
-    }
-
-    @Override
-    public File getTraceFile() {
-        return new File(mContext.getFilesDir(), "launcher_trace.pb");
-    }
-
-    @Override
-    public LauncherTraceFileProto.Builder getEncapsulatingTraceProto() {
-        return LauncherTraceFileProto.newBuilder();
-    }
-
-    @Override
-    public LauncherTraceEntryProto.Builder updateBufferProto(
-            LauncherTraceEntryProto.Builder reuseObj,
-            ArrayList<ProtoTraceable<LauncherTraceProto.Builder>> traceables) {
-        Trace.beginSection("ProtoTracer.updateBufferProto");
-        LauncherTraceEntryProto.Builder proto = LauncherTraceEntryProto.newBuilder();
-        proto.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
-        LauncherTraceProto.Builder launcherProto = LauncherTraceProto.newBuilder();
-        for (ProtoTraceable t : traceables) {
-            t.writeToProto(launcherProto);
-        }
-        proto.setLauncher(launcherProto);
-        Trace.endSection();
-        return proto;
-    }
-
-    @Override
-    public byte[] serializeEncapsulatingProto(LauncherTraceFileProto.Builder encapsulatingProto,
-            Queue<LauncherTraceEntryProto.Builder> buffer) {
-        Trace.beginSection("ProtoTracer.serializeEncapsulatingProto");
-        encapsulatingProto.setMagicNumber(MAGIC_NUMBER_VALUE);
-        for (LauncherTraceEntryProto.Builder entry : buffer) {
-            encapsulatingProto.addEntry(entry);
-        }
-        byte[] bytes = encapsulatingProto.build().toByteArray();
-        Trace.endSection();
-        return bytes;
-    }
-
-    @Override
-    public byte[] getProtoBytes(MessageLite.Builder proto) {
-        return proto.build().toByteArray();
-    }
-
-    @Override
-    public int getProtoSize(MessageLite.Builder proto) {
-        return proto.build().getSerializedSize();
-    }
-
-    public void start() {
-        mProtoTracer.start();
-    }
-
-    public void stop() {
-        mProtoTracer.stop();
-    }
-
-    public void add(ProtoTraceable<LauncherTraceProto.Builder> traceable) {
-        mProtoTracer.add(traceable);
-    }
-
-    public void remove(ProtoTraceable<LauncherTraceProto.Builder> traceable) {
-        mProtoTracer.remove(traceable);
-    }
-
-    public void scheduleFrameUpdate() {
-        mProtoTracer.scheduleFrameUpdate();
-    }
-
-    public void update() {
-        mProtoTracer.update();
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
index b76fe5c..5740991 100644
--- a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
@@ -17,14 +17,24 @@
 
 package com.android.quickstep.util
 
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.AnimatorSet
 import android.animation.ObjectAnimator
 import android.graphics.Bitmap
+import android.graphics.Rect
+import android.graphics.RectF
 import android.graphics.drawable.Drawable
 import android.view.View
 import com.android.launcher3.DeviceProfile
+import com.android.launcher3.Launcher
+import com.android.launcher3.Utilities
 import com.android.launcher3.anim.PendingAnimation
+import com.android.launcher3.dragndrop.DragLayer
 import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource
+import com.android.quickstep.views.FloatingTaskView
 import com.android.quickstep.views.IconView
+import com.android.quickstep.views.RecentsView
 import com.android.quickstep.views.TaskThumbnailView
 import com.android.quickstep.views.TaskView
 import com.android.quickstep.views.TaskView.TaskIdAttributeContainer
@@ -176,4 +186,53 @@
                     TaskThumbnailView.SPLIT_SELECT_TRANSLATE_X, 0f))
         }
     }
+
+    /** Does not play any animation if user is not currently in split selection state. */
+    fun playPlaceholderDismissAnim(launcher: Launcher) {
+        if (!splitSelectStateController.isSplitSelectActive) {
+            return
+        }
+
+        val anim = createPlaceholderDismissAnim(launcher)
+        anim.addListener(object : AnimatorListenerAdapter() {
+            override fun onAnimationEnd(animation: Animator) {
+                splitSelectStateController.resetState()
+            }
+        })
+        anim.start()
+    }
+
+    /** Returns [AnimatorSet] which slides initial split placeholder view offscreen. */
+    fun createPlaceholderDismissAnim(launcher: Launcher) : AnimatorSet {
+        val animatorSet = AnimatorSet()
+        val recentsView : RecentsView<*, *> = launcher.getOverviewPanel()
+        val floatingTask: FloatingTaskView = splitSelectStateController.firstFloatingTaskView
+                ?: return animatorSet
+
+        // We are in split selection state currently, transitioning to another state
+        val dragLayer: DragLayer = launcher.dragLayer
+        val onScreenRectF = RectF()
+        Utilities.getBoundsForViewInDragLayer(launcher.dragLayer, floatingTask,
+                Rect(0, 0, floatingTask.width, floatingTask.height),
+                false, null, onScreenRectF)
+        // Get the part of the floatingTask that intersects with the DragLayer (i.e. the
+        // on-screen portion)
+        onScreenRectF.intersect(
+                dragLayer.left.toFloat(),
+                dragLayer.top.toFloat(),
+                dragLayer.right.toFloat(),
+                dragLayer.bottom
+                        .toFloat()
+        )
+        animatorSet.play(ObjectAnimator.ofFloat(floatingTask,
+                FloatingTaskView.PRIMARY_TRANSLATE_OFFSCREEN,
+                recentsView.pagedOrientationHandler
+                        .getFloatingTaskOffscreenTranslationTarget(
+                                floatingTask,
+                                onScreenRectF,
+                                floatingTask.stagePosition,
+                                launcher.deviceProfile
+                        )))
+        return animatorSet
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index e063b44..970cf64 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -561,7 +561,7 @@
                 new RemoteSplitLaunchTransitionRunner(firstTaskId, secondTaskId, callback);
         final RemoteTransition remoteTransition = new RemoteTransition(animationRunner,
                 ActivityThread.currentActivityThread().getApplicationThread(),
-                "LaunchSplitPair");
+                "LaunchAppFullscreen");
         InstanceId instanceId = LogUtils.getShellShareableInstanceId().first;
         if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
             switch (launchData.getSplitLaunchType()) {
@@ -895,6 +895,7 @@
         mFirstFloatingTaskView = floatingTaskView;
     }
 
+    @Nullable
     public FloatingTaskView getFirstFloatingTaskView() {
         return mFirstFloatingTaskView;
     }
diff --git a/quickstep/src/com/android/quickstep/util/SplitWithKeyboardShortcutController.java b/quickstep/src/com/android/quickstep/util/SplitWithKeyboardShortcutController.java
index 24d8326..f3fa86a 100644
--- a/quickstep/src/com/android/quickstep/util/SplitWithKeyboardShortcutController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitWithKeyboardShortcutController.java
@@ -27,21 +27,15 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.app.ActivityManager;
-import android.content.Intent;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.os.SystemClock;
-import android.os.UserHandle;
-import android.view.View;
 
 import androidx.annotation.BinderThread;
 
-import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.PendingAnimation;
-import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
-import com.android.quickstep.OverviewCommandHelper;
 import com.android.quickstep.OverviewComponentObserver;
 import com.android.quickstep.RecentsAnimationCallbacks;
 import com.android.quickstep.RecentsAnimationController;
@@ -143,11 +137,7 @@
                     .updateIconInBackground(
                             Task.from(new Task.TaskKey(runningTaskInfo), runningTaskInfo,
                                     false /* isLocked */),
-                            (task) -> {
-                                if (task.thumbnail != null) {
-                                    floatingTaskView.setIcon(task.thumbnail.thumbnail);
-                                }
-                            });
+                            (task) -> floatingTaskView.setIcon(task.icon));
             floatingTaskView.setAlpha(1);
             floatingTaskView.addStagingAnimation(anim, startingTaskRect, mTempRect,
                     false /* fadeWithThumbnail */, true /* isStagedTask */);
diff --git a/quickstep/src/com/android/quickstep/views/FloatingTaskView.java b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
index a5652dc..f250b8c 100644
--- a/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
@@ -11,7 +11,6 @@
 import android.graphics.Paint;
 import android.graphics.Rect;
 import android.graphics.RectF;
-import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.util.FloatProperty;
@@ -213,8 +212,8 @@
         mSplitPlaceholderView.getIconView().setRotation(mOrientationHandler.getDegreesRotated());
     }
 
-    public void setIcon(Bitmap icon) {
-        mSplitPlaceholderView.setIcon(new BitmapDrawable(icon), mSplitHolderSize);
+    public void setIcon(Drawable drawable) {
+        mSplitPlaceholderView.setIcon(drawable, mSplitHolderSize);
     }
 
     protected void initPosition(RectF pos, InsettableFrameLayout.LayoutParams lp) {
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
index c91b183..01f6ae8 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
@@ -223,11 +223,12 @@
         // Callbacks run from remote animation when recents animation not currently running
         InteractionJankMonitorWrapper.begin(this,
                 InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER, "Enter form GroupedTaskView");
-        launchTask(success -> {
+        launchTaskInternal(success -> {
             endCallback.executeAllAndDestroy();
             InteractionJankMonitorWrapper.end(
                     InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER);
-        }, false /* freezeTaskList */);
+        }, false /* freezeTaskList */, true /*launchingExistingTaskview*/);
+
 
         // Callbacks get run from recentsView for case when recents animation already running
         recentsView.addSideTaskLaunchCallback(endCallback);
@@ -236,7 +237,19 @@
 
     @Override
     public void launchTask(@NonNull Consumer<Boolean> callback, boolean isQuickswitch) {
-        getRecentsView().getSplitSelectController().launchExistingSplitPair(this, mTask.key.id,
+        launchTaskInternal(callback, isQuickswitch, false /*launchingExistingTaskview*/);
+    }
+
+    /**
+     * @param launchingExistingTaskView {@link SplitSelectStateController#launchExistingSplitPair}
+     * uses existence of GroupedTaskView as control flow of how to animate in the incoming task. If
+     * we're launching from overview (from overview thumbnails) then pass in {@code true},
+     * otherwise pass in {@code false} for case like quickswitching from home to task
+     */
+    private void launchTaskInternal(@NonNull Consumer<Boolean> callback, boolean isQuickswitch,
+            boolean launchingExistingTaskView) {
+        getRecentsView().getSplitSelectController().launchExistingSplitPair(
+                launchingExistingTaskView ? this : null, mTask.key.id,
                 mSecondaryTask.key.id, SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT,
                 callback, isQuickswitch, getSplitRatio());
     }
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 3ee9009..80e5a54 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -86,12 +86,16 @@
         StateManager stateManager = mActivity.getStateManager();
         animated &= stateManager.shouldAnimateStateChange();
         stateManager.goToState(NORMAL, animated);
+        if (FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
+            mSplitSelectStateController.getSplitAnimationController()
+                    .playPlaceholderDismissAnim(mActivity);
+        }
         AbstractFloatingView.closeAllOpenViews(mActivity, animated);
     }
 
     @Override
-    protected boolean isCommandQueueEmpty() {
-        return mActivity.isCommandQueueEmpty();
+    protected boolean canStartHomeSafely() {
+        return mActivity.canStartHomeSafely();
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 421a48c..6f16f41 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -43,7 +43,6 @@
 import static com.android.launcher3.Utilities.squaredHypot;
 import static com.android.launcher3.Utilities.squaredTouchSlop;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_GRID_ONLY_OVERVIEW;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_LAUNCH_FROM_STAGED_APP;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_ACTIONS_SPLIT;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_CLEAR_ALL;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_DISMISS_SWIPE_UP;
@@ -54,6 +53,7 @@
 import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
 import static com.android.launcher3.util.SystemUiController.UI_STATE_FULLSCREEN_TASK;
 import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
+import static com.android.quickstep.util.LogUtils.splitFailureMessage;
 import static com.android.quickstep.views.ClearAllButton.DISMISS_ALPHA;
 import static com.android.quickstep.views.DesktopTaskView.DESKTOP_MODE_SUPPORTED;
 import static com.android.quickstep.views.OverviewActionsView.FLAG_IS_NOT_TABLET;
@@ -165,6 +165,7 @@
 import com.android.launcher3.util.ViewPool;
 import com.android.quickstep.BaseActivityInterface;
 import com.android.quickstep.GestureState;
+import com.android.quickstep.OverviewCommandHelper;
 import com.android.quickstep.RecentsAnimationController;
 import com.android.quickstep.RecentsAnimationTargets;
 import com.android.quickstep.RecentsFilterState;
@@ -2367,14 +2368,14 @@
     }
 
     public void startHome(boolean animated) {
-        if (!isCommandQueueEmpty()) return;
+        if (!canStartHomeSafely()) return;
         handleStartHome(animated);
     }
 
     protected abstract void handleStartHome(boolean animated);
 
-    /** Returns whether the overview command helper queue is empty. */
-    protected abstract boolean isCommandQueueEmpty();
+    /** Returns whether user can start home based on state in {@link OverviewCommandHelper}. */
+    protected abstract boolean canStartHomeSafely();
 
     public void reset() {
         setCurrentTask(-1);
@@ -3242,9 +3243,7 @@
         mSplitSelectStateController.setFirstFloatingTaskView(firstFloatingTaskView);
 
         // Allow user to click staged app to launch into fullscreen
-        if (ENABLE_LAUNCH_FROM_STAGED_APP.get()) {
-            firstFloatingTaskView.setOnClickListener(this::animateToFullscreen);
-        }
+        firstFloatingTaskView.setOnClickListener(this::animateToFullscreen);
 
         // SplitInstructionsView: animate in
         safeRemoveDragLayerView(mSplitInstructionsView);
@@ -4725,6 +4724,8 @@
             return false;
         }
         if (mSplitSelectStateController.isBothSplitAppsConfirmed()) {
+            Log.w(TAG, splitFailureMessage(
+                    "confirmSplitSelect", "both apps have already been set"));
             return true;
         }
         // Second task is selected either as an already-running Task or an Intent
@@ -4732,6 +4733,9 @@
             if (!task.isDockable) {
                 // Task does not support split screen
                 mSplitUnsupportedToast.show();
+                Log.w(TAG, splitFailureMessage("confirmSplitSelect",
+                        "selected Task (" + task.key.getPackageName()
+                                + ") is not dockable / does not support splitscreen"));
                 return true;
             }
             mSplitSelectStateController.setSecondTask(task);
@@ -6020,11 +6024,6 @@
     }
 
     @Nullable
-    public FloatingTaskView getFirstFloatingTaskView() {
-        return mSplitSelectStateController.getFirstFloatingTaskView();
-    }
-
-    @Nullable
     public SplitInstructionsView getSplitInstructionsView() {
         return mSplitInstructionsView;
     }
diff --git a/quickstep/tests/src/com/android/launcher3/model/QuickstepModelDelegateTest.kt b/quickstep/tests/src/com/android/launcher3/model/QuickstepModelDelegateTest.kt
new file mode 100644
index 0000000..a532762
--- /dev/null
+++ b/quickstep/tests/src/com/android/launcher3/model/QuickstepModelDelegateTest.kt
@@ -0,0 +1,127 @@
+package com.android.launcher3.model
+
+import android.app.prediction.AppPredictor
+import android.app.prediction.AppTarget
+import android.app.prediction.AppTargetEvent
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.launcher3.LauncherAppState
+import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION
+import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PREDICTION
+import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WALLPAPERS
+import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION
+import com.android.launcher3.util.LauncherModelHelper
+import org.junit.After
+import org.junit.Assert.assertNotSame
+import org.junit.Assert.assertSame
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.MockitoAnnotations
+
+/** Unit tests for [QuickstepModelDelegate]. */
+@RunWith(AndroidJUnit4::class)
+class QuickstepModelDelegateTest {
+
+    private lateinit var underTest: QuickstepModelDelegate
+    private lateinit var modelHelper: LauncherModelHelper
+
+    @Mock private lateinit var target: AppTarget
+    @Mock private lateinit var mockedAppTargetEvent: AppTargetEvent
+    @Mock private lateinit var allAppsPredictor: AppPredictor
+    @Mock private lateinit var hotseatPredictor: AppPredictor
+    @Mock private lateinit var widgetRecommendationPredictor: AppPredictor
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        modelHelper = LauncherModelHelper()
+        underTest = QuickstepModelDelegate(modelHelper.sandboxContext)
+        underTest.mAllAppsState.predictor = allAppsPredictor
+        underTest.mHotseatState.predictor = hotseatPredictor
+        underTest.mWidgetsRecommendationState.predictor = widgetRecommendationPredictor
+        underTest.mApp = LauncherAppState.getInstance(modelHelper.sandboxContext)
+        underTest.mDataModel = BgDataModel()
+    }
+
+    @After
+    fun tearDown() {
+        modelHelper.destroy()
+    }
+
+    @Test
+    fun onAppTargetEvent_notifyTarget() {
+        underTest.onAppTargetEvent(mockedAppTargetEvent, CONTAINER_PREDICTION)
+
+        verify(allAppsPredictor).notifyAppTargetEvent(mockedAppTargetEvent)
+        verifyZeroInteractions(hotseatPredictor)
+        verifyZeroInteractions(widgetRecommendationPredictor)
+    }
+
+    @Test
+    fun onWidgetPrediction_notifyWidgetRecommendationPredictor() {
+        underTest.onAppTargetEvent(mockedAppTargetEvent, CONTAINER_WIDGETS_PREDICTION)
+
+        verifyZeroInteractions(allAppsPredictor)
+        verify(widgetRecommendationPredictor).notifyAppTargetEvent(mockedAppTargetEvent)
+        verifyZeroInteractions(hotseatPredictor)
+    }
+
+    @Test
+    fun onHotseatPrediction_notifyHotseatPredictor() {
+        underTest.onAppTargetEvent(mockedAppTargetEvent, CONTAINER_HOTSEAT_PREDICTION)
+
+        verifyZeroInteractions(allAppsPredictor)
+        verifyZeroInteractions(widgetRecommendationPredictor)
+        verify(hotseatPredictor).notifyAppTargetEvent(mockedAppTargetEvent)
+    }
+
+    @Test
+    fun onOtherClient_notifyHotseatPredictor() {
+        underTest.onAppTargetEvent(mockedAppTargetEvent, CONTAINER_WALLPAPERS)
+
+        verifyZeroInteractions(allAppsPredictor)
+        verifyZeroInteractions(widgetRecommendationPredictor)
+        verify(hotseatPredictor).notifyAppTargetEvent(mockedAppTargetEvent)
+    }
+
+    @Test
+    fun hotseatActionPin_recreateHotSeat() {
+        assertSame(underTest.mHotseatState.predictor, hotseatPredictor)
+        val appTargetEvent = AppTargetEvent.Builder(target, AppTargetEvent.ACTION_PIN).build()
+        underTest.markActive()
+
+        underTest.onAppTargetEvent(appTargetEvent, CONTAINER_HOTSEAT_PREDICTION)
+
+        verify(hotseatPredictor).destroy()
+        assertNotSame(underTest.mHotseatState.predictor, hotseatPredictor)
+    }
+
+    @Test
+    fun hotseatActionUnpin_recreateHotSeat() {
+        assertSame(underTest.mHotseatState.predictor, hotseatPredictor)
+        underTest.markActive()
+        val appTargetEvent = AppTargetEvent.Builder(target, AppTargetEvent.ACTION_UNPIN).build()
+
+        underTest.onAppTargetEvent(appTargetEvent, CONTAINER_HOTSEAT_PREDICTION)
+
+        verify(hotseatPredictor).destroy()
+        assertNotSame(underTest.mHotseatState.predictor, hotseatPredictor)
+    }
+
+    @Test
+    fun container_actionPin_notRecreateHotSeat() {
+        assertSame(underTest.mHotseatState.predictor, hotseatPredictor)
+        val appTargetEvent = AppTargetEvent.Builder(target, AppTargetEvent.ACTION_UNPIN).build()
+        underTest.markActive()
+
+        underTest.onAppTargetEvent(appTargetEvent, CONTAINER_PREDICTION)
+
+        verify(allAppsPredictor, never()).destroy()
+        verify(hotseatPredictor, never()).destroy()
+        assertSame(underTest.mHotseatState.predictor, hotseatPredictor)
+    }
+}
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactoryTest.kt b/quickstep/tests/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactoryTest.kt
index 236b5db..3920b08 100644
--- a/quickstep/tests/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactoryTest.kt
+++ b/quickstep/tests/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactoryTest.kt
@@ -1,6 +1,9 @@
 package com.android.launcher3.taskbar.navbutton
 
 import android.content.res.Resources
+import android.view.Surface
+import android.view.Surface.ROTATION_270
+import android.view.Surface.Rotation
 import android.view.View
 import android.view.ViewGroup
 import android.widget.FrameLayout
@@ -32,6 +35,8 @@
     @Mock lateinit var mockRecentsButton: ImageView
     @Mock lateinit var mockHomeButton: ImageView
 
+    private var surfaceRotation = Surface.ROTATION_0
+
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
@@ -60,7 +65,8 @@
                 isKidsMode = true,
                 isInSetup = false,
                 isThreeButtonNav = false,
-                phoneMode = false
+                phoneMode = false,
+                surfaceRotation = surfaceRotation
             )
         assert(layoutter is KidsNavLayoutter)
     }
@@ -74,7 +80,8 @@
                 isKidsMode = false,
                 isInSetup = true,
                 isThreeButtonNav = false,
-                phoneMode = false
+                phoneMode = false,
+                surfaceRotation = surfaceRotation
             )
         assert(layoutter is SetupNavLayoutter)
     }
@@ -88,7 +95,8 @@
                 isKidsMode = false,
                 isInSetup = false,
                 isThreeButtonNav = false,
-                phoneMode = false
+                phoneMode = false,
+                surfaceRotation = surfaceRotation
             )
         assert(layoutter is TaskbarNavLayoutter)
     }
@@ -101,7 +109,8 @@
             isKidsMode = false,
             isInSetup = false,
             isThreeButtonNav = false,
-            phoneMode = false
+            phoneMode = false,
+            surfaceRotation = surfaceRotation
         )
     }
 
@@ -114,7 +123,8 @@
                 isKidsMode = false,
                 isInSetup = false,
                 isThreeButtonNav = true,
-                phoneMode = true
+                phoneMode = true,
+                surfaceRotation = surfaceRotation
             )
         assert(layoutter is PhonePortraitNavLayoutter)
     }
@@ -129,11 +139,28 @@
                 isKidsMode = false,
                 isInSetup = false,
                 isThreeButtonNav = true,
-                phoneMode = true
+                phoneMode = true,
+                surfaceRotation = surfaceRotation
             )
         assert(layoutter is PhoneLandscapeNavLayoutter)
     }
 
+    @Test
+    fun getTaskbarSeascapeLayoutter() {
+        assumeTrue(TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW)
+        mockDeviceProfile.isTaskbarPresent = false
+        setDeviceProfileLandscape()
+        val layoutter: NavButtonLayoutFactory.NavButtonLayoutter =
+                getLayoutter(
+                        isKidsMode = false,
+                        isInSetup = false,
+                        isThreeButtonNav = true,
+                        phoneMode = true,
+                        surfaceRotation = ROTATION_270
+                )
+        assert(layoutter is PhoneSeascapeNavLayoutter)
+    }
+
     @Test(expected = IllegalStateException::class)
     fun noValidLayoutForPhoneGestureNav() {
         assumeTrue(TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW)
@@ -142,7 +169,8 @@
             isKidsMode = false,
             isInSetup = false,
             isThreeButtonNav = false,
-            phoneMode = true
+            phoneMode = true,
+            surfaceRotation = surfaceRotation
         )
     }
 
@@ -157,7 +185,8 @@
         isKidsMode: Boolean,
         isInSetup: Boolean,
         isThreeButtonNav: Boolean,
-        phoneMode: Boolean
+        phoneMode: Boolean,
+        @Rotation surfaceRotation: Int
     ): NavButtonLayoutFactory.NavButtonLayoutter {
         return NavButtonLayoutFactory.getUiLayoutter(
             deviceProfile = mockDeviceProfile,
@@ -166,7 +195,8 @@
             isKidsMode = isKidsMode,
             isInSetup = isInSetup,
             isThreeButtonNav = isThreeButtonNav,
-            phoneMode = phoneMode
+            phoneMode = phoneMode,
+            surfaceRotation = surfaceRotation
         )
     }
 }
diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
index 7492ab8..a67d787 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -62,7 +62,6 @@
 import com.android.launcher3.util.rule.SamplerRule;
 import com.android.launcher3.util.rule.ScreenRecordRule;
 import com.android.launcher3.util.rule.TestStabilityRule;
-import com.android.launcher3.util.rule.ViewCaptureAnalysisRule;
 import com.android.launcher3.util.rule.ViewCaptureRule;
 import com.android.quickstep.views.RecentsView;
 
@@ -123,8 +122,7 @@
                 .outerRule(new SamplerRule())
                 .around(new NavigationModeSwitchRule(mLauncher))
                 .around(new FailureWatcher(mLauncher, viewCaptureRule::getViewCaptureData))
-                .around(viewCaptureRule)
-                .around(new ViewCaptureAnalysisRule(viewCaptureRule.getViewCapture()));
+                .around(viewCaptureRule);
 
         mOtherLauncherActivity = context.getPackageManager().queryIntentActivities(
                 getHomeIntentInPackage(context),
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsTrackpad.java b/quickstep/tests/src/com/android/quickstep/TaplTestsTrackpad.java
new file mode 100644
index 0000000..4c6874e
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsTrackpad.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assume.assumeTrue;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.launcher3.tapl.LauncherInstrumentation.TrackpadGestureType;
+import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
+import com.android.launcher3.ui.TaplTestsLauncher3;
+import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class TaplTestsTrackpad extends AbstractQuickStepTest {
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        TaplTestsLauncher3.initialize(this);
+    }
+
+    @After
+    public void tearDown() {
+        mLauncher.setTrackpadGestureType(TrackpadGestureType.NONE);
+    }
+
+    @Test
+    @PortraitLandscape
+    @NavigationModeSwitch
+    public void goHome() throws Exception {
+        assumeTrue(mLauncher.isTablet());
+
+        mLauncher.setTrackpadGestureType(TrackpadGestureType.THREE_FINGER);
+        startTestActivity(2);
+        mLauncher.goHome();
+    }
+
+    @Test
+    @PortraitLandscape
+    @NavigationModeSwitch
+    public void switchToOverview() throws Exception {
+        assumeTrue(mLauncher.isTablet());
+
+        mLauncher.setTrackpadGestureType(TrackpadGestureType.THREE_FINGER);
+        startTestActivity(2);
+        mLauncher.goHome().switchToOverview();
+    }
+
+    @Test
+    @PortraitLandscape
+    @NavigationModeSwitch
+    public void testAllAppsFromHome() throws Exception {
+        assumeTrue(mLauncher.isTablet());
+
+        mLauncher.setTrackpadGestureType(TrackpadGestureType.TWO_FINGER);
+        assertNotNull("switchToAllApps() returned null",
+                mLauncher.getWorkspace().switchToAllApps());
+    }
+}
diff --git a/res/color-v31/surface.xml b/res/color-v31/surface.xml
deleted file mode 100644
index da4571a..0000000
--- a/res/color-v31/surface.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2021, 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.
-*/
--->
-<selector
-    xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:color="?attr/materialColorSurfaceContainerHighest"/>
-</selector>
-
diff --git a/res/drawable/add_item_dialog_background.xml b/res/drawable/add_item_dialog_background.xml
index c3a8269..e279fa0 100644
--- a/res/drawable/add_item_dialog_background.xml
+++ b/res/drawable/add_item_dialog_background.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
     android:shape="rectangle" >
-    <solid android:color="@color/surface" />
+    <solid android:color="@color/material_color_surface_container_highest" />
     <corners
         android:topLeftRadius="?android:attr/dialogCornerRadius"
         android:topRightRadius="?android:attr/dialogCornerRadius" />
diff --git a/res/drawable/button_top_rounded_bordered_ripple.xml b/res/drawable/button_top_rounded_bordered_ripple.xml
index f15a4a0..f5b6886 100644
--- a/res/drawable/button_top_rounded_bordered_ripple.xml
+++ b/res/drawable/button_top_rounded_bordered_ripple.xml
@@ -25,7 +25,7 @@
                     android:topRightRadius="12dp"
                     android:bottomLeftRadius="4dp"
                     android:bottomRightRadius="4dp"  />
-                <solid android:color="@color/surface"/>
+                <solid android:color="@color/material_color_surface_container_highest"/>
                 <stroke
                     android:width="2dp"
                     android:color="@color/button_bg"/>
diff --git a/res/layout/hotseat.xml b/res/layout/hotseat.xml
index 95ebd94..82b0b8d 100644
--- a/res/layout/hotseat.xml
+++ b/res/layout/hotseat.xml
@@ -21,5 +21,4 @@
     android:layout_height="match_parent"
     android:theme="@style/HomeScreenElementTheme"
     android:importantForAccessibility="no"
-    android:preferKeepClear="true"
     launcher:containerType="hotseat" />
\ No newline at end of file
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index c39e1d6..1ec09f5 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -168,7 +168,7 @@
     <string name="work_apps_paused_edu_banner" msgid="8872412121608402058">"အလုပ်သုံးအက်ပ်များကို တံဆိပ်တပ်ထားပြီး သင်၏ IT စီမံခန့်ခွဲသူက မြင်နိုင်ပါသည်"</string>
     <string name="work_apps_paused_edu_accept" msgid="6377476824357318532">"နားလည်ပြီ"</string>
     <string name="work_apps_pause_btn_text" msgid="4669288269140620646">"အလုပ်သုံးအက်ပ်များကို ခဏရပ်ရန်"</string>
-    <string name="work_apps_enable_btn_text" msgid="1736198302467317371">"ပြန်စရန်"</string>
+    <string name="work_apps_enable_btn_text" msgid="1736198302467317371">"ပြန်ဖွင့်ရန်"</string>
     <string name="developer_options_filter_hint" msgid="5896817443635989056">"စစ်ထုတ်ရန်"</string>
     <string name="search_pref_screen_title" msgid="3258959643336315962">"သင့်ဖုန်းတွင် ရှာခြင်း"</string>
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"သင့်တက်ဘလက်ကို ရှာခြင်း"</string>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 76a1239..73e392d 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -85,6 +85,7 @@
             <enum name="taskbar" value="5" />
             <enum name="search_result_tall" value="6" />
             <enum name="search_result_small" value="7" />
+            <enum name="prediction_row" value="8" />
         </attr>
         <attr name="centerVertically" format="boolean" />
     </declare-styleable>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 786088e..6796d4b 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -438,4 +438,8 @@
     <!--  Folder spaces  -->
     <dimen name="folder_top_padding_default">24dp</dimen>
     <dimen name="folder_footer_horiz_padding">20dp</dimen>
+
+    <!-- Default Ime height. Used only for logging purposes.
+    Assume this is default keyboard height in EN locale in case the keyboard height is not known when queried.-->
+    <dimen name="default_ime_height">300dp</dimen>
 </resources>
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 05d434e..2356bcc 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -94,11 +94,12 @@
         IconLabelDotView, DraggableView, Reorderable {
 
     private static final int DISPLAY_WORKSPACE = 0;
-    private static final int DISPLAY_ALL_APPS = 1;
+    public static final int DISPLAY_ALL_APPS = 1;
     private static final int DISPLAY_FOLDER = 2;
     protected static final int DISPLAY_TASKBAR = 5;
     private static final int DISPLAY_SEARCH_RESULT = 6;
     private static final int DISPLAY_SEARCH_RESULT_SMALL = 7;
+    public static final int DISPLAY_PREDICTION_ROW = 8;
 
     private static final float MIN_LETTER_SPACING = -0.05f;
     private static final int MAX_SEARCH_LOOP_COUNT = 20;
@@ -211,7 +212,7 @@
             setCompoundDrawablePadding(grid.iconDrawablePaddingPx);
             defaultIconSize = grid.iconSizePx;
             setCenterVertically(grid.iconCenterVertically);
-        } else if (mDisplay == DISPLAY_ALL_APPS) {
+        } else if (mDisplay == DISPLAY_ALL_APPS || mDisplay == DISPLAY_PREDICTION_ROW) {
             setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.allAppsIconTextSizePx);
             setCompoundDrawablePadding(grid.allAppsIconDrawablePaddingPx);
             defaultIconSize = grid.allAppsIconSizePx;
@@ -402,7 +403,7 @@
      *  Only if actual text can be displayed in two line, the {@code true} value will be effective.
      */
     protected boolean shouldUseTwoLine() {
-        return (FeatureFlags.ENABLE_TWOLINE_ALLAPPS.get() && mDisplay == DISPLAY_ALL_APPS)
+        return  (FeatureFlags.ENABLE_TWOLINE_ALLAPPS.get() && mDisplay == DISPLAY_ALL_APPS)
                 || (FeatureFlags.ENABLE_TWOLINE_DEVICESEARCH.get()
                 && mDisplay == DISPLAY_SEARCH_RESULT);
     }
@@ -424,10 +425,10 @@
         }
     }
 
-    /** This is used for testing to forcefully set the display to ALL_APPS */
+    /** This is used for testing to forcefully set the display. */
     @VisibleForTesting
-    public void setDisplayAllApps() {
-        mDisplay = DISPLAY_ALL_APPS;
+    public void setDisplay(int display) {
+        mDisplay = display;
     }
 
     /**
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index bd47fca..f3b5155 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -164,7 +164,7 @@
     public int iconSizePx;
     public int iconTextSizePx;
     public int iconDrawablePaddingPx;
-    public int iconDrawablePaddingOriginalPx;
+    private final int mIconDrawablePaddingOriginalPx;
     public boolean iconCenterVertically;
 
     public float cellScaleToFit;
@@ -456,7 +456,7 @@
             cellStyle = context.obtainStyledAttributes(R.style.CellStyleDefault,
                     R.styleable.CellStyle);
         }
-        iconDrawablePaddingOriginalPx = cellStyle.getDimensionPixelSize(
+        mIconDrawablePaddingOriginalPx = cellStyle.getDimensionPixelSize(
                 R.styleable.CellStyle_iconDrawablePadding, 0);
         cellStyle.recycle();
 
@@ -883,7 +883,6 @@
         iconCenterVertically = mIsScalableGrid || mIsResponsiveGrid;
 
         updateIconSize(1f, res);
-
         updateWorkspacePadding();
 
         // Check to see if the icons fit within the available height.
@@ -925,6 +924,25 @@
                 + cellLayoutPaddingPx.left + cellLayoutPaddingPx.right;
     }
 
+    private int getNormalizedIconDrawablePadding() {
+        // TODO(b/235886078): workaround needed because of this bug
+        // Icons are 10% larger on XML than their visual size,
+        // so remove that extra space to get labels closer to the correct padding
+        int iconVisibleSizePx = (int) Math.round(ICON_VISIBLE_AREA_FACTOR * iconSizePx);
+        return Math.max(0, mIconDrawablePaddingOriginalPx - ((iconSizePx - iconVisibleSizePx) / 2));
+    }
+
+    private int getNormalizedFolderChildDrawablePaddingPx(int textHeight) {
+        // TODO(b/235886078): workaround needed because of this bug
+        // Icons are 10% larger on XML than their visual size,
+        // so remove that extra space to get labels closer to the correct padding
+        int drawablePadding = (folderCellHeightPx - folderChildIconSizePx - textHeight) / 3;
+
+        int iconVisibleSizePx = Math.round(ICON_VISIBLE_AREA_FACTOR * folderChildIconSizePx);
+        int iconSizeDiff = folderChildIconSizePx - iconVisibleSizePx;
+        return Math.max(0, drawablePadding - iconSizeDiff / 2);
+    }
+
     /**
      * Updating the iconSize affects many aspects of the launcher layout, such as: iconSizePx,
      * iconTextSizePx, iconDrawablePaddingPx, cellWidth/Height, allApps* variants,
@@ -937,43 +955,36 @@
 
         // Workspace
         final boolean isVerticalLayout = isVerticalBarLayout();
-        iconDrawablePaddingPx = (int) (iconDrawablePaddingOriginalPx * iconScale);
         cellLayoutBorderSpacePx = getCellLayoutBorderSpace(inv, scale);
-        int cellTextAndPaddingHeight =
-                iconDrawablePaddingPx + Utilities.calculateTextHeight(iconTextSizePx);
 
         if (mIsResponsiveGrid) {
-            int cellContentHeight = iconSizePx + cellTextAndPaddingHeight;
-
             cellWidthPx = mResponsiveWidthSpec.getCellSizePx();
             cellHeightPx = mResponsiveHeightSpec.getCellSizePx();
 
             if (cellWidthPx < iconSizePx) {
                 // get a smaller icon size
                 iconSizePx = mIconSizeSteps.getIconSmallerThan(cellWidthPx);
-                // calculate new cellContentHeight
-                cellContentHeight = iconSizePx + cellTextAndPaddingHeight;
             }
 
+            iconDrawablePaddingPx = getNormalizedIconDrawablePadding();
+            int iconTextHeight = Utilities.calculateTextHeight(iconTextSizePx);
+            int cellContentHeight = iconSizePx + iconDrawablePaddingPx + iconTextHeight;
+
             while (iconSizePx > mIconSizeSteps.minimumIconSize()
                     && cellContentHeight > cellHeightPx) {
-                int extraHeightRequired = cellContentHeight - cellHeightPx;
-                int newPadding = iconDrawablePaddingPx - extraHeightRequired;
-                if (newPadding >= 0) {
-                    // Responsive uses the padding without scaling
-                    iconDrawablePaddingPx = iconDrawablePaddingOriginalPx = newPadding;
-                    cellTextAndPaddingHeight =
-                            iconDrawablePaddingPx + Utilities.calculateTextHeight(iconTextSizePx);
-                } else {
+                iconDrawablePaddingPx -= cellContentHeight - cellHeightPx;
+                if (iconDrawablePaddingPx < 0) {
                     // get a smaller icon size
                     iconSizePx = mIconSizeSteps.getNextLowerIconSize(iconSizePx);
+                    iconDrawablePaddingPx = getNormalizedIconDrawablePadding();
                 }
                 // calculate new cellContentHeight
-                cellContentHeight = iconSizePx + cellTextAndPaddingHeight;
+                cellContentHeight = iconSizePx + iconDrawablePaddingPx + iconTextHeight;
             }
 
             cellYPaddingPx = Math.max(0, cellHeightPx - cellContentHeight) / 2;
         } else if (mIsScalableGrid) {
+            iconDrawablePaddingPx = (int) (getNormalizedIconDrawablePadding() * iconScale);
             cellWidthPx = pxFromDp(inv.minCellSize[mTypeIndex].x, mMetrics, scale);
             cellHeightPx = pxFromDp(inv.minCellSize[mTypeIndex].y, mMetrics, scale);
 
@@ -995,6 +1006,8 @@
                 }
             }
 
+            int cellTextAndPaddingHeight =
+                    iconDrawablePaddingPx + Utilities.calculateTextHeight(iconTextSizePx);
             int cellContentHeight = iconSizePx + cellTextAndPaddingHeight;
             if (cellHeightPx < cellContentHeight) {
                 // If cellHeight no longer fit iconSize, reduce borderSpace to make cellHeight
@@ -1030,6 +1043,7 @@
             desiredWorkspaceHorizontalMarginPx =
                     (int) (desiredWorkspaceHorizontalMarginOriginalPx * scale);
         } else {
+            iconDrawablePaddingPx = (int) (getNormalizedIconDrawablePadding() * iconScale);
             cellWidthPx = iconSizePx + iconDrawablePaddingPx;
             cellHeightPx = (int) Math.ceil(iconSizePx * ICON_OVERLAP_FACTOR)
                     + iconDrawablePaddingPx
@@ -1103,7 +1117,7 @@
         if (mIsScalableGrid) {
             allAppsIconSizePx = pxFromDp(inv.allAppsIconSize[mTypeIndex], mMetrics);
             allAppsIconTextSizePx = pxFromSp(inv.allAppsIconTextSize[mTypeIndex], mMetrics);
-            allAppsIconDrawablePaddingPx = iconDrawablePaddingOriginalPx;
+            allAppsIconDrawablePaddingPx = getNormalizedIconDrawablePadding();
             allAppsCellWidthPx = pxFromDp(inv.allAppsCellSize[mTypeIndex].x, mMetrics, scale);
 
             if (allAppsCellWidthPx < allAppsIconSizePx) {
@@ -1145,7 +1159,7 @@
     private void updateAllAppsWithResponsiveMeasures() {
         allAppsIconSizePx = iconSizePx;
         allAppsIconTextSizePx = iconTextSizePx;
-        allAppsIconDrawablePaddingPx = iconDrawablePaddingOriginalPx;
+        allAppsIconDrawablePaddingPx = iconDrawablePaddingPx;
 
         allAppsBorderSpacePx = new Point(
                 mAllAppsResponsiveWidthSpec.getGutterPx(),
@@ -1241,9 +1255,35 @@
             folderFooterHeightPx = mResponsiveFolderHeightSpec.getEndPaddingPx();
 
             folderCellLayoutBorderSpacePx = new Point(mResponsiveFolderWidthSpec.getGutterPx(),
-                    mResponsiveHeightSpec.getGutterPx());
+                    mResponsiveFolderHeightSpec.getGutterPx());
 
             folderContentPaddingLeftRight = mResponsiveFolderWidthSpec.getStartPaddingPx();
+
+            // Reduce icon width if it's wider than the expected folder cell width
+            if (folderCellWidthPx < folderChildIconSizePx) {
+                folderChildIconSizePx = mIconSizeSteps.getIconSmallerThan(folderCellWidthPx);
+            }
+
+            // Recalculating padding and cell height
+            folderChildDrawablePaddingPx = getNormalizedFolderChildDrawablePaddingPx(textHeight);
+            int folderCellContentHeight = folderChildIconSizePx + folderChildDrawablePaddingPx
+                    + textHeight;
+
+            // Reduce the icon in height when it's taller than the expected cell height
+            while (folderChildIconSizePx > mIconSizeSteps.minimumIconSize()
+                    && folderCellContentHeight > folderCellHeightPx) {
+                folderChildDrawablePaddingPx -= folderCellContentHeight - folderCellHeightPx;
+                if (folderChildDrawablePaddingPx < 0) {
+                    // get a smaller icon size
+                    folderChildIconSizePx = mIconSizeSteps.getNextLowerIconSize(
+                            folderChildIconSizePx);
+                    folderChildDrawablePaddingPx =
+                            getNormalizedFolderChildDrawablePaddingPx(textHeight);
+                }
+                // calculate new cellContentHeight
+                folderCellContentHeight = folderChildIconSizePx + folderChildDrawablePaddingPx
+                        + textHeight;
+            }
         } else if (mIsScalableGrid) {
             if (inv.folderStyle == INVALID_RESOURCE_HANDLE) {
                 folderCellWidthPx = roundPxValueFromFloat(getCellSize().x * scale);
@@ -1261,6 +1301,8 @@
             folderFooterHeightPx = roundPxValueFromFloat(folderFooterHeightPx * scale);
 
             folderContentPaddingLeftRight = folderCellLayoutBorderSpacePx.x;
+
+            folderChildDrawablePaddingPx = getNormalizedFolderChildDrawablePaddingPx(textHeight);
         } else {
             int cellPaddingX = (int) (res.getDimensionPixelSize(R.dimen.folder_cell_x_padding)
                     * scale);
@@ -1277,10 +1319,8 @@
                             res.getDimensionPixelSize(R.dimen.folder_footer_height_default)
                                     * scale);
 
+            folderChildDrawablePaddingPx = getNormalizedFolderChildDrawablePaddingPx(textHeight);
         }
-
-        folderChildDrawablePaddingPx = Math.max(0,
-                (folderCellHeightPx - folderChildIconSizePx - textHeight) / 3);
     }
 
     public void updateInsets(Rect insets) {
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 7e43002..d85c384 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -194,6 +194,7 @@
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.touch.AllAppsSwipeController;
+import com.android.launcher3.touch.ItemLongClickListener;
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
 import com.android.launcher3.util.ActivityResultInfo;
 import com.android.launcher3.util.ActivityTracker;
@@ -423,6 +424,7 @@
     @Override
     @TargetApi(Build.VERSION_CODES.S)
     protected void onCreate(Bundle savedInstanceState) {
+        TestProtocol.testLogD(TestProtocol.ACTIVITY_LIFECYCLE_RULE, "Launcher.onCreate 1");
         mStartupLatencyLogger = createStartupLatencyLogger(
                 sIsNewProcess
                         ? LockedUserState.get(this).isUserUnlockedAtLauncherStartup()
@@ -581,6 +583,7 @@
         }
         setTitle(R.string.home_screen);
         mStartupLatencyLogger.logEnd(LAUNCHER_LATENCY_STARTUP_ACTIVITY_ON_CREATE);
+        TestProtocol.testLogD(TestProtocol.ACTIVITY_LIFECYCLE_RULE, "Launcher.onCreate 2");
     }
 
     /**
@@ -1055,6 +1058,7 @@
 
     @Override
     protected void onStop() {
+        TestProtocol.testLogD(TestProtocol.ACTIVITY_LIFECYCLE_RULE, "Launcher.onStop 1");
         super.onStop();
         if (mDeferOverlayCallbacks) {
             checkIfOverlayStillDeferred();
@@ -1066,10 +1070,12 @@
         mAppWidgetHolder.setActivityStarted(false);
         NotificationListener.removeNotificationsChangedListener(getPopupDataProvider());
         FloatingIconView.resetIconLoadResult();
+        TestProtocol.testLogD(TestProtocol.ACTIVITY_LIFECYCLE_RULE, "Launcher.onStop 2");
     }
 
     @Override
     protected void onStart() {
+        TestProtocol.testLogD(TestProtocol.ACTIVITY_LIFECYCLE_RULE, "Launcher.onStart 1");
         TraceHelper.INSTANCE.beginSection(ON_START_EVT);
         super.onStart();
         if (!mDeferOverlayCallbacks) {
@@ -1078,6 +1084,7 @@
 
         mAppWidgetHolder.setActivityStarted(true);
         TraceHelper.INSTANCE.endSection();
+        TestProtocol.testLogD(TestProtocol.ACTIVITY_LIFECYCLE_RULE, "Launcher.onStart 2");
     }
 
     @Override
@@ -1248,6 +1255,7 @@
 
     @Override
     protected void onResume() {
+        TestProtocol.testLogD(TestProtocol.ACTIVITY_LIFECYCLE_RULE, "Launcher.onResume 1");
         TraceHelper.INSTANCE.beginSection(ON_RESUME_EVT);
         super.onResume();
 
@@ -1259,10 +1267,12 @@
 
         DragView.removeAllViews(this);
         TraceHelper.INSTANCE.endSection();
+        TestProtocol.testLogD(TestProtocol.ACTIVITY_LIFECYCLE_RULE, "Launcher.onResume 2");
     }
 
     @Override
     protected void onPause() {
+        TestProtocol.testLogD(TestProtocol.ACTIVITY_LIFECYCLE_RULE, "Launcher.onPause 1");
         // Ensure that items added to Launcher are queued until Launcher returns
         ItemInstallQueue.INSTANCE.get(this).pauseModelPush(FLAG_ACTIVITY_PAUSED);
 
@@ -1275,6 +1285,7 @@
             mOverlayManager.onActivityPaused(this);
         }
         mAppWidgetHolder.setActivityResumed(false);
+        TestProtocol.testLogD(TestProtocol.ACTIVITY_LIFECYCLE_RULE, "Launcher.onPause 2");
     }
 
     /**
@@ -1407,8 +1418,7 @@
      */
     protected void completeAddShortcut(Intent data, int container, int screenId, int cellX,
             int cellY, PendingRequestArgs args) {
-        if (args.getRequestCode() != REQUEST_CREATE_SHORTCUT
-                || args.getPendingIntent().getComponent() == null) {
+        if (args.getRequestCode() != REQUEST_CREATE_SHORTCUT) {
             return;
         }
 
@@ -1683,6 +1693,9 @@
             if (mLauncherCallbacks != null) {
                 mLauncherCallbacks.onHomeIntent(internalStateHandled);
             }
+            if (FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
+                handleSplitAnimationGoingToHome();
+            }
             mOverlayManager.hideOverlay(isStarted() && !isForceInvisible());
             handleGestureContract(intent);
         } else if (Intent.ACTION_ALL_APPS.equals(intent.getAction())) {
@@ -1696,6 +1709,11 @@
         TraceHelper.INSTANCE.endSection();
     }
 
+    /** Handle animating away split placeholder view when user taps on home button */
+    protected void handleSplitAnimationGoingToHome() {
+        // Overridden
+    }
+
     protected void toggleAllAppsFromIntent(boolean alreadyOnHome) {
         if (getStateManager().isInStableState(ALL_APPS)) {
             getStateManager().goToState(NORMAL, alreadyOnHome);
@@ -1740,6 +1758,8 @@
 
     @Override
     protected void onSaveInstanceState(Bundle outState) {
+        TestProtocol.testLogD(
+                TestProtocol.ACTIVITY_LIFECYCLE_RULE, "Launcher.onSaveInstanceState 1");
         outState.putIntArray(RUNTIME_STATE_CURRENT_SCREEN_IDS,
                 mWorkspace.getCurrentPageScreenIds().getArray().toArray());
         outState.putInt(RUNTIME_STATE, mStateManager.getState().ordinal);
@@ -1771,10 +1791,13 @@
 
         super.onSaveInstanceState(outState);
         mOverlayManager.onActivitySaveInstanceState(this, outState);
+        TestProtocol.testLogD(
+                TestProtocol.ACTIVITY_LIFECYCLE_RULE, "Launcher.onSaveInstanceState 2");
     }
 
     @Override
     public void onDestroy() {
+        TestProtocol.testLogD(TestProtocol.ACTIVITY_LIFECYCLE_RULE, "Launcher.onDestroy 1");
         super.onDestroy();
         ACTIVITY_TRACKER.onActivityDestroyed(this);
 
@@ -1797,6 +1820,7 @@
         LauncherAppState.getIDP(this).removeOnChangeListener(this);
 
         mOverlayManager.onActivityDestroyed(this);
+        TestProtocol.testLogD(TestProtocol.ACTIVITY_LIFECYCLE_RULE, "Launcher.onDestroy 2");
     }
 
     public LauncherAccessibilityDelegate getAccessibilityDelegate() {
@@ -1916,16 +1940,10 @@
         info.spanX = spanX;
         info.spanY = spanY;
 
-        switch (info.itemType) {
-            case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
-            case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
-                addAppWidgetFromDrop((PendingAddWidgetInfo) info);
-                break;
-            case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
-                processShortcutFromDrop((PendingAddShortcutInfo) info);
-                break;
-            default:
-                throw new IllegalStateException("Unknown item type: " + info.itemType);
+        if (info instanceof PendingAddWidgetInfo) {
+            addAppWidgetFromDrop((PendingAddWidgetInfo) info);
+        } else { // info can only be PendingAddShortcutInfo
+            processShortcutFromDrop((PendingAddShortcutInfo) info);
         }
     }
 
@@ -2182,6 +2200,8 @@
 
     /**
      * Returns the CellLayout of the specified container at the specified screen.
+     *
+     * @param screenId must be presenterPos and not modelPos.
      */
     public CellLayout getCellLayout(int container, int screenId) {
         return (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT)
@@ -2967,7 +2987,7 @@
             Map<PackageUserKey, Integer> packageUserKeytoUidMap) {
         Preconditions.assertUIThread();
         boolean hadWorkApps = mAppsView.shouldShowTabs();
-        AllAppsStore appsStore = mAppsView.getAppsStore();
+        AllAppsStore<Launcher> appsStore = mAppsView.getAppsStore();
         appsStore.setApps(apps, flags, packageUserKeytoUidMap);
         PopupContainerWithArrow.dismissInvalidPopup(this);
         if (hadWorkApps != mAppsView.shouldShowTabs()) {
@@ -3379,4 +3399,9 @@
     public boolean areFreeformTasksVisible() {
         return false; // Base launcher does not track freeform tasks
     }
+
+    @Override
+    public View.OnLongClickListener getAllAppsItemLongClickListener() {
+        return ItemLongClickListener.INSTANCE_ALL_APPS;
+    }
 }
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index ddafd53..5f3d27c 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -20,9 +20,7 @@
 
 import static com.android.launcher3.LauncherAppState.ACTION_FORCE_ROLOAD;
 import static com.android.launcher3.config.FeatureFlags.IS_STUDIO_BUILD;
-import static com.android.launcher3.testing.shared.TestProtocol.WORK_TAB_MISSING;
 import static com.android.launcher3.testing.shared.TestProtocol.sDebugTracing;
-import static com.android.launcher3.testing.shared.TestProtocol.testLogD;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
@@ -69,7 +67,6 @@
 import com.android.launcher3.pm.PackageInstallInfo;
 import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.shortcuts.ShortcutRequest;
-import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.PackageUserKey;
@@ -310,11 +307,6 @@
      * @see UserCache#addUserEventListener
      */
     public void onUserEvent(UserHandle user, String action) {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.WORK_TAB_MISSING, "onBroadcastIntent intentAction: "
-                    + action + " user: " + user);
-        }
-
         if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action)
                 || Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action)) {
             enqueueModelUpdateTask(new PackageUpdatedTask(
@@ -562,7 +554,6 @@
             synchronized (mLock) {
                 // Everything loaded bind the data.
                 mModelLoaded = true;
-                testLogD(WORK_TAB_MISSING, "launcher model loaded");
             }
         }
 
diff --git a/src/com/android/launcher3/LauncherPrefs.kt b/src/com/android/launcher3/LauncherPrefs.kt
index 177f883..427eaa3 100644
--- a/src/com/android/launcher3/LauncherPrefs.kt
+++ b/src/com/android/launcher3/LauncherPrefs.kt
@@ -277,7 +277,7 @@
         @JvmField val ICON_STATE = nonRestorableItem(LauncherAppState.KEY_ICON_STATE, "", true)
         @JvmField
         val ALL_APPS_OVERVIEW_THRESHOLD =
-            nonRestorableItem(LauncherAppState.KEY_ALL_APPS_OVERVIEW_THRESHOLD, 200, true)
+            nonRestorableItem(LauncherAppState.KEY_ALL_APPS_OVERVIEW_THRESHOLD, 180, true)
         @JvmField val THEMED_ICONS = backedUpItem(Themes.KEY_THEMED_ICONS, false, true)
         @JvmField val PROMISE_ICON_IDS = backedUpItem(InstallSessionHelper.PROMISE_ICON_IDS, "")
         @JvmField val WORK_EDU_STEP = backedUpItem(WorkProfileManager.KEY_WORK_EDU_STEP, 0)
diff --git a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
index 4590125..eb4ecaf 100644
--- a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
@@ -18,10 +18,10 @@
 import static com.android.launcher3.allapps.ActivityAllAppsContainerView.AdapterHolder.SEARCH;
 import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_WORK_DISABLED_CARD;
 import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_WORK_EDU_CARD;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_ALL_APPS_RV_PREINFLATION;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_COUNT;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_TAP_ON_PERSONAL_TAB;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_TAP_ON_WORK_TAB;
-import static com.android.launcher3.testing.shared.TestProtocol.WORK_TAB_MISSING;
 import static com.android.launcher3.util.ScrollableLayoutManager.PREDICTIVE_BACK_MIN_SCALE;
 
 import android.animation.Animator;
@@ -45,7 +45,6 @@
 import android.util.FloatProperty;
 import android.util.Log;
 import android.util.SparseArray;
-import android.util.TypedValue;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
@@ -78,8 +77,6 @@
 import com.android.launcher3.keyboard.FocusedItemDecorator;
 import com.android.launcher3.model.StringCache;
 import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.testing.shared.TestProtocol;
-import com.android.launcher3.util.Executors;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.ActivityContext;
@@ -141,7 +138,7 @@
     private final SearchTransitionController mSearchTransitionController;
     private final Paint mHeaderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
     private final Rect mInsets = new Rect();
-    private final AllAppsStore mAllAppsStore = new AllAppsStore();
+    private final AllAppsStore<T> mAllAppsStore;
     private final RecyclerView.OnScrollListener mScrollListener =
             new RecyclerView.OnScrollListener() {
                 @Override
@@ -194,6 +191,7 @@
     public ActivityAllAppsContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
         mActivityContext = ActivityContext.lookupContext(context);
+        mAllAppsStore = new AllAppsStore<>(mActivityContext);
 
         mScrimColor = Themes.getAttrColor(context, R.attr.allAppsScrimColor);
         mHeaderThreshold = getResources().getDimensionPixelSize(
@@ -208,10 +206,6 @@
         mNavBarScrimPaint.setColor(Themes.getNavBarScrimColor(mActivityContext));
 
         AllAppsStore.OnUpdateListener onAppsUpdated = this::onAppsUpdated;
-        if (TestProtocol.sDebugTracing) {
-            Log.d(WORK_TAB_MISSING, "ActivityAllAppsContainer#init registeringListener: " +
-                    onAppsUpdated);
-        }
         mAllAppsStore.addUpdateListener(onAppsUpdated);
 
         // This is a focus listener that proxies focus from a view into the list view.  This is to
@@ -289,9 +283,8 @@
                 0,
                 0 // Bottom left
         };
-        final TypedValue value = new TypedValue();
-        getContext().getTheme().resolveAttribute(android.R.attr.colorBackground, value, true);
-        mBottomSheetBackgroundColor = value.data;
+        mBottomSheetBackgroundColor =
+                Themes.getAttrColor(getContext(), R.attr.materialColorSurfaceDim);
         updateBackgroundVisibility(mActivityContext.getDeviceProfile());
         mSearchUiManager.initializeSearch(this);
     }
@@ -559,6 +552,13 @@
             mAH.get(AdapterHolder.MAIN).setup(mViewPager.getChildAt(0), mPersonalMatcher);
             mAH.get(AdapterHolder.WORK).setup(mViewPager.getChildAt(1), mWorkManager.getMatcher());
             mAH.get(AdapterHolder.WORK).mRecyclerView.setId(R.id.apps_list_view_work);
+            if (ENABLE_ALL_APPS_RV_PREINFLATION.get()) {
+                // Let main and work rv share same view pool.
+                ((RecyclerView) mViewPager.getChildAt(0))
+                        .setRecycledViewPool(mAllAppsStore.getRecyclerViewPool());
+                ((RecyclerView) mViewPager.getChildAt(1))
+                        .setRecycledViewPool(mAllAppsStore.getRecyclerViewPool());
+            }
             if (FeatureFlags.ENABLE_EXPANDING_PAUSE_WORK_BUTTON.get()) {
                 mAH.get(AdapterHolder.WORK).mRecyclerView.addOnScrollListener(
                         mWorkManager.newScrollListener());
@@ -890,16 +890,7 @@
         container.put(R.id.work_tab_state_id, state);
     }
 
-    /**
-     * Sets the long click listener for icons
-     */
-    public void setOnIconLongClickListener(OnLongClickListener listener) {
-        for (AdapterHolder holder : mAH) {
-            holder.mAdapter.setOnIconLongClickListener(listener);
-        }
-    }
-
-    public AllAppsStore getAppsStore() {
+    public AllAppsStore<T> getAppsStore() {
         return mAllAppsStore;
     }
 
@@ -942,10 +933,6 @@
 
     private void onAppsUpdated() {
         mHasWorkApps = Stream.of(mAllAppsStore.getApps()).anyMatch(mWorkManager.getMatcher());
-        if (TestProtocol.sDebugTracing) {
-            Log.d(WORK_TAB_MISSING, "ActivityAllAppsContainerView#onAppsUpdated hasWorkApps: " +
-                    mHasWorkApps + " allApps: " + mAllAppsStore.getApps().length);
-        }
         if (!isSearching()) {
             rebindAdapters();
             if (mHasWorkApps) {
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index 7c5c003..602d1a3 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -96,8 +96,8 @@
         int approxRows = (int) Math.ceil(grid.availableHeightPx / grid.allAppsIconSizePx);
         pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_EMPTY_SEARCH, 1);
         pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_ALL_APPS_DIVIDER, 1);
-        pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_ICON, approxRows
-                * (mNumAppsPerRow + 1));
+        pool.setMaxRecycledViews(
+                AllAppsGridAdapter.VIEW_TYPE_ICON, (approxRows + 1) * grid.numShownAllAppsColumns);
     }
 
     @Override
diff --git a/src/com/android/launcher3/allapps/AllAppsStore.java b/src/com/android/launcher3/allapps/AllAppsStore.java
index 06af970..c3d0e6b 100644
--- a/src/com/android/launcher3/allapps/AllAppsStore.java
+++ b/src/com/android/launcher3/allapps/AllAppsStore.java
@@ -15,24 +15,27 @@
  */
 package com.android.launcher3.allapps;
 
+import static com.android.launcher3.config.FeatureFlags.ENABLE_ALL_APPS_RV_PREINFLATION;
 import static com.android.launcher3.model.data.AppInfo.COMPONENT_KEY_COMPARATOR;
 import static com.android.launcher3.model.data.AppInfo.EMPTY_ARRAY;
 import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_SHOW_DOWNLOAD_PROGRESS_MASK;
-import static com.android.launcher3.testing.shared.TestProtocol.WORK_TAB_MISSING;
 
+import android.content.Context;
 import android.os.UserHandle;
-import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.RecyclerView.RecycledViewPool;
 
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.testing.shared.TestProtocol;
+import com.android.launcher3.recyclerview.AllAppsRecyclerViewPool;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.PackageUserKey;
+import com.android.launcher3.views.ActivityContext;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -45,8 +48,10 @@
 
 /**
  * A utility class to maintain the collection of all apps.
+ *
+ * @param <T> The type of the context.
  */
-public class AllAppsStore {
+public class AllAppsStore<T extends Context & ActivityContext> {
 
     // Defer updates flag used to defer all apps updates to the next draw.
     public static final int DEFER_UPDATES_NEXT_DRAW = 1 << 0;
@@ -64,20 +69,36 @@
     private int mModelFlags;
     private int mDeferUpdatesFlags = 0;
     private boolean mUpdatePending = false;
+    private final AllAppsRecyclerViewPool mAllAppsRecyclerViewPool = new AllAppsRecyclerViewPool();
+
+    private final T mContext;
 
     public AppInfo[] getApps() {
         return mApps;
     }
 
+    public AllAppsStore(@NonNull T context) {
+        mContext = context;
+    }
+
     /**
      * Sets the current set of apps and sets mapping for {@link PackageUserKey} to Uid for
      * the current set of apps.
      */
-    public void setApps(AppInfo[] apps, int flags, Map<PackageUserKey, Integer> map) {
+    public void setApps(AppInfo[] apps, int flags, Map<PackageUserKey, Integer> map)  {
         mApps = apps;
         mModelFlags = flags;
         notifyUpdate();
         mPackageUserKeytoUidMap = map;
+        // Preinflate all apps RV when apps has changed, which can happen after unlocking screen,
+        // rotating screen, or downloading/upgrading apps.
+        if (ENABLE_ALL_APPS_RV_PREINFLATION.get()) {
+            mAllAppsRecyclerViewPool.preInflateAllAppsViewHolders(mContext);
+        }
+    }
+
+    RecycledViewPool getRecyclerViewPool() {
+        return mAllAppsRecyclerViewPool;
     }
 
     /**
@@ -134,9 +155,6 @@
             return;
         }
         for (OnUpdateListener listener : mUpdateListeners) {
-            if (TestProtocol.sDebugTracing) {
-                Log.d(WORK_TAB_MISSING, "AllAppsStore#notifyUpdate listener: " + listener);
-            }
             listener.onAppsUpdated();
         }
     }
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index 29767bf..0657178 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -69,7 +69,7 @@
     // The set of apps from the system
     private final List<AppInfo> mApps = new ArrayList<>();
     @Nullable
-    private final AllAppsStore mAllAppsStore;
+    private final AllAppsStore<T> mAllAppsStore;
 
     // The number of results in current adapter
     private int mAccessibilityResultsCount = 0;
@@ -86,7 +86,7 @@
     private int mNumAppRowsInAdapter;
     private Predicate<ItemInfo> mItemFilter;
 
-    public AlphabeticalAppsList(Context context, @Nullable AllAppsStore appsStore,
+    public AlphabeticalAppsList(Context context, @Nullable AllAppsStore<T> appsStore,
             WorkProfileManager workProfileManager) {
         mAllAppsStore = appsStore;
         mActivityContext = ActivityContext.lookupContext(context);
diff --git a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
index 8fa4276..be0a898 100644
--- a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
+++ b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
@@ -15,10 +15,7 @@
  */
 package com.android.launcher3.allapps;
 
-import static com.android.launcher3.touch.ItemLongClickListener.INSTANCE_ALL_APPS;
-
 import android.content.Context;
-import android.content.res.Resources;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.View.OnClickListener;
@@ -27,13 +24,12 @@
 import android.view.ViewGroup;
 import android.widget.TextView;
 
-import androidx.annotation.Nullable;
 import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.R;
-import com.android.launcher3.allapps.search.SearchAdapterProvider;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.allapps.search.SearchAdapterProvider;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.views.ActivityContext;
@@ -139,31 +135,24 @@
 
     protected final LayoutInflater mLayoutInflater;
     protected final OnClickListener mOnIconClickListener;
-    protected OnLongClickListener mOnIconLongClickListener = INSTANCE_ALL_APPS;
+    protected final OnLongClickListener mOnIconLongClickListener;
     protected OnFocusChangeListener mIconFocusListener;
     private final int mExtraTextHeight;
 
     public BaseAllAppsAdapter(T activityContext, LayoutInflater inflater,
             AlphabeticalAppsList<T> apps, SearchAdapterProvider<?> adapterProvider) {
-        Resources res = activityContext.getResources();
         mActivityContext = activityContext;
         mApps = apps;
         mLayoutInflater = inflater;
 
         mOnIconClickListener = mActivityContext.getItemOnClickListener();
+        mOnIconLongClickListener = mActivityContext.getAllAppsItemLongClickListener();
 
         mAdapterProvider = adapterProvider;
         mExtraTextHeight = Utilities.calculateTextHeight(
                 mActivityContext.getDeviceProfile().allAppsIconTextSizePx);
     }
 
-    /**
-     * Sets the long click listener for icons
-     */
-    public void setOnIconLongClickListener(@Nullable OnLongClickListener listener) {
-        mOnIconLongClickListener = listener;
-    }
-
     /** Checks if the passed viewType represents all apps divider. */
     public static boolean isDividerViewType(int viewType) {
         return isViewType(viewType, VIEW_TYPE_MASK_DIVIDER);
diff --git a/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java b/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
index d78e453..5e48177 100644
--- a/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
@@ -102,6 +102,12 @@
 
         StateManager<LauncherState> stateManager = mActivityContext.getStateManager();
 
+        // Special case to not expand the search bar when exiting All Apps on phones.
+        if (stateManager.getCurrentStableState() == LauncherState.ALL_APPS
+                && mActivityContext.getDeviceProfile().isPhone) {
+            return LauncherState.ALL_APPS.getFloatingSearchBarRestingMarginStart(mActivityContext);
+        }
+
         if (stateManager.isInTransition() && stateManager.getTargetState() != null) {
             return stateManager.getTargetState()
                     .getFloatingSearchBarRestingMarginStart(mActivityContext);
@@ -118,6 +124,12 @@
 
         StateManager<LauncherState> stateManager = mActivityContext.getStateManager();
 
+        // Special case to not expand the search bar when exiting All Apps on phones.
+        if (stateManager.getCurrentStableState() == LauncherState.ALL_APPS
+                && mActivityContext.getDeviceProfile().isPhone) {
+            return LauncherState.ALL_APPS.getFloatingSearchBarRestingMarginEnd(mActivityContext);
+        }
+
         if (stateManager.isInTransition() && stateManager.getTargetState() != null) {
             return stateManager.getTargetState()
                     .getFloatingSearchBarRestingMarginEnd(mActivityContext);
diff --git a/src/com/android/launcher3/allapps/WorkProfileManager.java b/src/com/android/launcher3/allapps/WorkProfileManager.java
index 44c233f..1ac8d87 100644
--- a/src/com/android/launcher3/allapps/WorkProfileManager.java
+++ b/src/com/android/launcher3/allapps/WorkProfileManager.java
@@ -25,7 +25,6 @@
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_HAS_SHORTCUT_PERMISSION;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_CHANGE_PERMISSION;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED;
-import static com.android.launcher3.testing.shared.TestProtocol.WORK_TAB_MISSING;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 
 import android.os.Build;
@@ -47,7 +46,6 @@
 import com.android.launcher3.allapps.BaseAllAppsAdapter.AdapterItem;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip;
 
 import java.lang.annotation.Retention;
@@ -144,10 +142,6 @@
     }
 
     private void updateCurrentState(@WorkProfileState int currentState) {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(WORK_TAB_MISSING, "WorkProfileManager#updateCurrentState: " +
-                    currentState, new Throwable());
-        }
         mCurrentState = currentState;
         if (getAH() != null) {
             getAH().mAppsList.updateAdapterItems();
@@ -166,10 +160,6 @@
      * Creates and attaches for profile toggle button to {@link ActivityAllAppsContainerView}
      */
     public boolean attachWorkModeSwitch() {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(WORK_TAB_MISSING, "ActivityAllAppsContainerView#attachWorkModeSwitch "
-                    + "mWorkModeSwitch: " + mWorkModeSwitch);
-        }
         if (!mAllApps.getAppsStore().hasModelFlag(
                 FLAG_HAS_SHORTCUT_PERMISSION | FLAG_QUIET_MODE_CHANGE_PERMISSION)) {
             Log.e(TAG, "unable to attach work mode switch; Missing required permissions");
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index df24620..5426532 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -182,6 +182,11 @@
             "ENABLE_BACK_SWIPE_LAUNCHER_ANIMATION", DISABLED,
             "Enables predictive back animation from all apps and widgets to home");
 
+    // TODO(Block 11): Clean up flags
+    public static final BooleanFlag ENABLE_PARAMETRIZE_REORDER = getDebugFlag(289420844,
+            "ENABLE_PARAMETRIZE_REORDER", DISABLED,
+            "Enables generating the reorder using a set of parameters");
+
     // TODO(Block 12): Clean up flags
     public static final BooleanFlag ENABLE_MULTI_INSTANCE = getDebugFlag(270396680,
             "ENABLE_MULTI_INSTANCE", DISABLED,
@@ -250,11 +255,6 @@
                     + "taskbar flavors");
 
     // TODO(Block 18): Clean up flags
-    public static final BooleanFlag ENABLE_LAUNCH_FROM_STAGED_APP = getDebugFlag(270395567,
-            "ENABLE_LAUNCH_FROM_STAGED_APP", ENABLED,
-            "Enable the ability to tap a staged app during split select to launch it in full "
-                    + "screen");
-
     public static final BooleanFlag ENABLE_APP_PAIRS = getDebugFlag(274189428,
             "ENABLE_APP_PAIRS", DISABLED,
             "Enables the ability to create and save app pairs on the Home screen for easy"
@@ -358,10 +358,6 @@
             "Use inbuilt monochrome icons if app doesn't provide one");
 
     // TODO(Block 28): Clean up flags
-    public static final BooleanFlag ENABLE_SPLIT_FROM_WORKSPACE = getDebugFlag(270393906,
-            "ENABLE_SPLIT_FROM_WORKSPACE", ENABLED,
-            "Enable initiating split screen from workspace.");
-
     public static final BooleanFlag ENABLE_SPLIT_FROM_FULLSCREEN_WITH_KEYBOARD_SHORTCUTS =
             getDebugFlag(270394122, "ENABLE_SPLIT_FROM_FULLSCREEN_SHORTCUT", DISABLED,
                     "Enable splitting from fullscreen app with keyboard shortcuts");
@@ -390,17 +386,23 @@
             "USE_SEARCH_REQUEST_TIMEOUT_OVERRIDES", DISABLED,
             "Use local overrides for search request timeout");
 
-    // TODO(Block 31)
+    // TODO(Block 31): Clean up flags
     public static final BooleanFlag ENABLE_SPLIT_LAUNCH_DATA_REFACTOR = getDebugFlag(279494325,
             "ENABLE_SPLIT_LAUNCH_DATA_REFACTOR", ENABLED,
             "Use refactored split launching code path");
 
-    // TODO(Block 32): Empty block
-
+    // TODO(Block 32): Clean up flags
     public static final BooleanFlag ENABLE_RESPONSIVE_WORKSPACE = getDebugFlag(241386436,
             "ENABLE_RESPONSIVE_WORKSPACE", DISABLED,
             "Enables new workspace grid calculations method.");
 
+    // TODO(Block 33): Clean up flags
+    public static final BooleanFlag ENABLE_ALL_APPS_RV_PREINFLATION = getDebugFlag(288161355,
+            "ENABLE_ALL_APPS_RV_PREINFLATION", DISABLED,
+            "Enables preinflating all apps icons to avoid scrolling jank.");
+
+    // TODO(Block 34): Empty block
+
     public static class BooleanFlag {
 
         private final boolean mCurrentValue;
diff --git a/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
index 7bdec1c..0f3cad6 100644
--- a/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
+++ b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.dragndrop;
 
+import static android.content.pm.LauncherApps.EXTRA_PIN_ITEM_REQUEST;
+
 import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
 import static com.android.launcher3.LauncherState.EDIT_MODE;
 import static com.android.launcher3.LauncherState.SPRING_LOADED;
@@ -25,6 +27,7 @@
 import android.app.Activity;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
 import android.content.pm.LauncherApps;
 import android.content.pm.LauncherApps.PinItemRequest;
 import android.content.pm.PackageManager;
@@ -41,6 +44,7 @@
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.pm.PinRequestHelper;
 import com.android.launcher3.pm.ShortcutConfigActivityInfo;
+import com.android.launcher3.util.StartActivityParams;
 
 import java.util.function.Supplier;
 
@@ -105,7 +109,11 @@
 
     @Override
     public boolean startConfigActivity(Activity activity, int requestCode) {
-        return false;
+        new StartActivityParams(activity, requestCode).deliverResult(
+                activity,
+                Activity.RESULT_OK,
+                new Intent().putExtra(EXTRA_PIN_ITEM_REQUEST, mRequestSupplier.get()));
+        return true;
     }
 
     @Override
diff --git a/src/com/android/launcher3/folder/LauncherDelegate.java b/src/com/android/launcher3/folder/LauncherDelegate.java
index f15dc83..7ac2472 100644
--- a/src/com/android/launcher3/folder/LauncherDelegate.java
+++ b/src/com/android/launcher3/folder/LauncherDelegate.java
@@ -93,7 +93,7 @@
                         // Move the item from the folder to the workspace, in the position of the
                         // folder
                         CellLayout cellLayout = mLauncher.getCellLayout(info.container,
-                                info.screenId);
+                                mLauncher.getCellPosMapper().mapModelToPresenter(info).screenId);
                         if (cellLayout == null) {
                             return;
                         }
diff --git a/src/com/android/launcher3/graphics/SysUiScrim.java b/src/com/android/launcher3/graphics/SysUiScrim.java
index a572a60..66001d8 100644
--- a/src/com/android/launcher3/graphics/SysUiScrim.java
+++ b/src/com/android/launcher3/graphics/SysUiScrim.java
@@ -32,6 +32,7 @@
 import android.view.View;
 
 import androidx.annotation.ColorInt;
+import androidx.annotation.VisibleForTesting;
 
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.DeviceProfile;
@@ -87,6 +88,7 @@
     private final View mRoot;
     private final BaseDraggingActivity mActivity;
     private final boolean mHideSysUiScrim;
+    private boolean mSkipScrimAnimationForTest = false;
 
     private boolean mAnimateScrimOnNextDraw = false;
     private final AnimatedFloat mSysUiAnimMultiplier = new AnimatedFloat(this::reapplySysUiAlpha);
@@ -189,6 +191,15 @@
         mBottomMaskRect.set(0, h - mBottomMaskHeight, w, h);
     }
 
+    /**
+     * Sets whether the SysUiScrim should hide for testing.
+     */
+    @VisibleForTesting
+    public void skipScrimAnimation() {
+        mSkipScrimAnimationForTest = true;
+        reapplySysUiAlpha();
+    }
+
     private void reapplySysUiAlpha() {
         reapplySysUiAlphaNoInvalidate();
         if (!mHideSysUiScrim) {
@@ -198,6 +209,7 @@
 
     private void reapplySysUiAlphaNoInvalidate() {
         float factor = mSysUiProgress.value * mSysUiAnimMultiplier.value;
+        if (mSkipScrimAnimationForTest) factor = 1f;
         mBottomMaskPaint.setAlpha(Math.round(MAX_SYSUI_SCRIM_ALPHA * factor));
         mTopMaskPaint.setAlpha(Math.round(MAX_SYSUI_SCRIM_ALPHA * factor));
     }
diff --git a/src/com/android/launcher3/logging/KeyboardStateManager.java b/src/com/android/launcher3/logging/KeyboardStateManager.java
index 6dc0a0b..d0f9c74 100644
--- a/src/com/android/launcher3/logging/KeyboardStateManager.java
+++ b/src/com/android/launcher3/logging/KeyboardStateManager.java
@@ -24,7 +24,10 @@
  */
 public class KeyboardStateManager {
     private long mUpdatedTime;
-    private int mImeHeight;
+    private int mImeHeightPx;
+    // Height of the keyboard when it's shown.
+    // mImeShownHeightPx>=mImeHeightPx always.
+    private int mImeShownHeightPx;
 
     public enum KeyboardState {
         NO_IME_ACTION,
@@ -34,8 +37,9 @@
 
     private KeyboardState mKeyboardState;
 
-    public KeyboardStateManager() {
+    public KeyboardStateManager(int defaultImeShownHeightPx) {
         mKeyboardState = NO_IME_ACTION;
+        mImeShownHeightPx = defaultImeShownHeightPx;
     }
 
     /**
@@ -64,13 +68,25 @@
      * Returns keyboard's current height.
      */
     public int getImeHeight() {
-        return mImeHeight;
+        return mImeHeightPx;
     }
 
     /**
-     * Setter method to set keyboard height.
+     * Returns keyboard's height in pixels when shown.
      */
-    public void setImeHeight(int imeHeight) {
-        mImeHeight = imeHeight;
+    public int getImeShownHeight() {
+        return mImeShownHeightPx;
+    }
+
+    /**
+     * Setter method to set keyboard height in pixels.
+     */
+    public void setImeHeight(int imeHeightPx) {
+        mImeHeightPx = imeHeightPx;
+        if (mImeHeightPx > 0) {
+            // Update the mImeShownHeightPx with the actual ime height when shown and store it
+            // for future sessions.
+            mImeShownHeightPx = mImeHeightPx;
+        }
     }
 }
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index 7e065a9..3e9731382 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -59,6 +59,7 @@
     private InstanceId mInstanceId;
 
     protected @Nullable ActivityContext mActivityContext = null;
+    protected @Nullable Context mContext = null;
     private KeyboardStateManager mKeyboardStateManager;
 
     /**
@@ -1035,7 +1036,9 @@
      */
     public KeyboardStateManager keyboardStateManager() {
         if (mKeyboardStateManager == null) {
-            mKeyboardStateManager = new KeyboardStateManager();
+            mKeyboardStateManager = new KeyboardStateManager(
+                    mContext != null ? mContext.getResources().getDimensionPixelSize(
+                            R.dimen.default_ime_height) : 0);
         }
         return mKeyboardStateManager;
     }
@@ -1071,6 +1074,7 @@
         StatsLogManager manager = Overrides.getObject(StatsLogManager.class,
                 context.getApplicationContext(), R.string.stats_log_manager_class);
         manager.mActivityContext = ActivityContext.lookupContextNoThrow(context);
+        manager.mContext = context;
         return manager;
     }
 }
diff --git a/src/com/android/launcher3/model/BaseModelUpdateTask.java b/src/com/android/launcher3/model/BaseModelUpdateTask.java
index 866e222..97f540e 100644
--- a/src/com/android/launcher3/model/BaseModelUpdateTask.java
+++ b/src/com/android/launcher3/model/BaseModelUpdateTask.java
@@ -15,9 +15,6 @@
  */
 package com.android.launcher3.model;
 
-import static com.android.launcher3.testing.shared.TestProtocol.WORK_TAB_MISSING;
-import static com.android.launcher3.testing.shared.TestProtocol.testLogD;
-
 import android.util.Log;
 
 import androidx.annotation.NonNull;
@@ -33,7 +30,6 @@
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.widget.model.WidgetsListBaseEntry;
@@ -77,7 +73,6 @@
     @Override
     public final void run() {
         boolean isModelLoaded = Objects.requireNonNull(mModel).isModelLoaded();
-        testLogD(WORK_TAB_MISSING, "modelLoaded: " + isModelLoaded + " forTask: " + this);
         if (!isModelLoaded) {
             if (DEBUG_TASKS) {
                 Log.d(TAG, "Ignoring model task since loader is pending=" + this);
@@ -115,10 +110,6 @@
         List<WorkspaceItemInfo> workspaceUpdates = allUpdates.stream()
                 .filter(info -> info.id != ItemInfo.NO_ID)
                 .collect(Collectors.toList());
-        if (TestProtocol.sDebugTracing) {
-            Log.d(WORK_TAB_MISSING, "allUpdates: " + allUpdates.size() + ", workspaceUpdates "
-                    + workspaceUpdates.size());
-        }
         if (!workspaceUpdates.isEmpty()) {
             scheduleCallbackTask(c -> c.bindWorkspaceItemsChanged(workspaceUpdates));
         }
@@ -157,12 +148,7 @@
     }
 
     public void bindApplicationsIfNeeded() {
-        boolean changeFlag = mAllAppsList.getAndResetChangeFlag();
-        if (TestProtocol.sDebugTracing) {
-            Log.d(WORK_TAB_MISSING, "bindApplicationsIfNeeded changeFlag? " +
-                    changeFlag);
-        }
-        if (changeFlag) {
+        if (mAllAppsList.getAndResetChangeFlag()) {
             AppInfo[] apps = mAllAppsList.copyData();
             int flags = mAllAppsList.getFlags();
             Map<PackageUserKey, Integer> packageUserKeytoUidMap = Arrays.stream(apps).collect(
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 3daf4af..787ac38 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -24,8 +24,6 @@
 import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_DISABLED_LOCKED_USER;
 import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_DISABLED_SAFEMODE;
 import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_DISABLED_SUSPENDED;
-import static com.android.launcher3.testing.shared.TestProtocol.WORK_TAB_MISSING;
-import static com.android.launcher3.testing.shared.TestProtocol.testLogD;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission;
 import static com.android.launcher3.util.PackageManagerHelper.isSystemApp;
@@ -932,7 +930,6 @@
     }
 
     private List<LauncherActivityInfo> loadAllApps() {
-        testLogD(WORK_TAB_MISSING, "loadingAllApps");
         final List<UserHandle> profiles = mUserCache.getUserProfiles();
         List<LauncherActivityInfo> allActivityList = new ArrayList<>();
         // Clear the list of apps
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index 8c938f4..2591550 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -93,11 +93,6 @@
         mOp = op;
         mUser = user;
         mPackages = packages;
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.WORK_TAB_MISSING, "PackageUpdatedTask mOp: " + mOp +
-                    " packageCount: " + mPackages.length + " user: " + user);
-            DEBUG = true;
-        }
     }
 
     @Override
@@ -142,9 +137,6 @@
                         // The update may have changed which shortcuts/widgets are available.
                         // Refresh the widgets for the package if we have an activity running.
                         Launcher launcher = Launcher.ACTIVITY_TRACKER.getCreatedActivity();
-                        if (TestProtocol.sDebugTracing) {
-                            Log.d(TestProtocol.WORK_TAB_MISSING, "launcher: " + launcher);
-                        }
                         if (launcher != null) {
                             launcher.refreshAndBindWidgetsForPackageUser(
                                     new PackageUserKey(packages[i], mUser));
diff --git a/src/com/android/launcher3/model/UserLockStateChangedTask.java b/src/com/android/launcher3/model/UserLockStateChangedTask.java
index cb78138..63ca35b 100644
--- a/src/com/android/launcher3/model/UserLockStateChangedTask.java
+++ b/src/com/android/launcher3/model/UserLockStateChangedTask.java
@@ -16,12 +16,10 @@
 package com.android.launcher3.model;
 
 import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_DISABLED_LOCKED_USER;
-import static com.android.launcher3.testing.shared.TestProtocol.WORK_TAB_MISSING;
 
 import android.content.Context;
 import android.content.pm.ShortcutInfo;
 import android.os.UserHandle;
-import android.util.Log;
 
 import androidx.annotation.NonNull;
 
@@ -31,7 +29,6 @@
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.shortcuts.ShortcutRequest;
 import com.android.launcher3.shortcuts.ShortcutRequest.QueryResult;
-import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.ItemInfoMatcher;
 
@@ -63,10 +60,6 @@
         if (mIsUserUnlocked) {
             QueryResult shortcuts = new ShortcutRequest(context, mUser)
                     .query(ShortcutRequest.PINNED);
-            if (TestProtocol.sDebugTracing) {
-                Log.d(WORK_TAB_MISSING, "shortcutQuery success? "
-                        + shortcuts.wasSuccess());
-            }
             if (shortcuts.wasSuccess()) {
                 for (ShortcutInfo shortcut : shortcuts) {
                     pinnedShortcuts.put(ShortcutKey.fromInfo(shortcut), shortcut);
@@ -89,9 +82,6 @@
                     if (mIsUserUnlocked) {
                         ShortcutKey key = ShortcutKey.fromItemInfo(si);
                         ShortcutInfo shortcut = pinnedShortcuts.get(key);
-                        if (TestProtocol.sDebugTracing) {
-                            Log.d(WORK_TAB_MISSING, "shortcutInfo: " + shortcut);
-                        }
                         // We couldn't verify the shortcut during loader. If its no longer available
                         // (probably due to clear data), delete the workspace item as well
                         if (shortcut == null) {
diff --git a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
index b2c64b3..073e523 100644
--- a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
+++ b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
@@ -381,7 +381,9 @@
 
         // Draw all page indicators;
         float circleGap = mCircleGap;
-        float startX = (getWidth() - (mNumPages * circleGap) + mDotRadius) / 2;
+        float startX = ((float) getWidth() / 2)
+                - (mCircleGap * (((float) mNumPages - 1) / 2))
+                - mDotRadius;
 
         float x = startX + mDotRadius;
         float y = getHeight() / 2;
@@ -420,9 +422,9 @@
         float startCircle = (int) mCurrentPosition;
         float delta = mCurrentPosition - startCircle;
         float diameter = 2 * mDotRadius;
-        float startX;
-
-        startX = ((getWidth() - (mNumPages * mCircleGap) + mDotRadius) / 2);
+        float startX = ((float) getWidth() / 2)
+                - (mCircleGap * (((float) mNumPages - 1) / 2))
+                - mDotRadius;
         sTempRect.top = (getHeight() * 0.5f) - mDotRadius;
         sTempRect.bottom = (getHeight() * 0.5f) + mDotRadius;
         sTempRect.left = startX + (startCircle * mCircleGap);
diff --git a/src/com/android/launcher3/pm/UserCache.java b/src/com/android/launcher3/pm/UserCache.java
index c313886..92822ab 100644
--- a/src/com/android/launcher3/pm/UserCache.java
+++ b/src/com/android/launcher3/pm/UserCache.java
@@ -17,8 +17,6 @@
 package com.android.launcher3.pm;
 
 import static com.android.launcher3.Utilities.ATLEAST_U;
-import static com.android.launcher3.testing.shared.TestProtocol.WORK_TAB_MISSING;
-import static com.android.launcher3.testing.shared.TestProtocol.testLogD;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
 import android.content.Context;
@@ -94,8 +92,6 @@
 
     @AnyThread
     private void onUsersChanged(Intent intent) {
-        testLogD(WORK_TAB_MISSING, "onUsersChanged intent: " + intent);
-
         MODEL_EXECUTOR.execute(this::updateCache);
         UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER);
         if (user == null) {
diff --git a/src/com/android/launcher3/popup/ArrowPopup.java b/src/com/android/launcher3/popup/ArrowPopup.java
index 08be026..e0f245f 100644
--- a/src/com/android/launcher3/popup/ArrowPopup.java
+++ b/src/com/android/launcher3/popup/ArrowPopup.java
@@ -673,10 +673,6 @@
         }
         mIsOpen = false;
 
-        mOpenCloseAnimator = getOpenCloseAnimator(false, mCloseDuration, mCloseFadeStartDelay,
-                mCloseFadeDuration, mCloseChildFadeStartDelay, mCloseChildFadeDuration,
-                ACCELERATED_EASE);
-
         mOpenCloseAnimator = ENABLE_MATERIAL_U_POPUP.get()
                 ? getMaterialUOpenCloseAnimator(
                         false,
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 8274789..1f26bab 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -125,6 +125,14 @@
         this(context, null, 0);
     }
 
+    @Override
+    protected View getAccessibilityInitialFocusView() {
+        if (mSystemShortcutContainer != null) {
+            return mSystemShortcutContainer.getChildAt(0);
+        }
+        return super.getAccessibilityInitialFocusView();
+    }
+
     public LauncherAccessibilityDelegate getAccessibilityDelegate() {
         return mAccessibilityDelegate;
     }
@@ -242,7 +250,6 @@
                     popupDataProvider.getNotificationKeysForItem(item),
                     systemShortcuts);
         }
-        launcher.tryClearAccessibilityFocus(icon);
         launcher.refreshAndBindWidgetsForPackageUser(PackageUserKey.fromItemInfo(item));
         container.requestFocus();
         return container;
diff --git a/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt b/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt
new file mode 100644
index 0000000..26dde29
--- /dev/null
+++ b/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.recyclerview
+
+import android.content.Context
+import androidx.recyclerview.widget.RecyclerView
+import androidx.recyclerview.widget.RecyclerView.RecycledViewPool
+import androidx.recyclerview.widget.RecyclerView.ViewHolder
+import com.android.launcher3.BubbleTextView
+import com.android.launcher3.allapps.BaseAllAppsAdapter
+import com.android.launcher3.util.Executors.MAIN_EXECUTOR
+import com.android.launcher3.util.Executors.VIEW_PREINFLATION_EXECUTOR
+import com.android.launcher3.views.ActivityContext
+import java.util.concurrent.Future
+
+private const val PREINFLATE_ICONS_ROW_COUNT = 4
+private const val EXTRA_ICONS_COUNT = 2
+
+/**
+ * An [RecycledViewPool] that preinflates app icons ([ViewHolder] of [BubbleTextView]) of all apps
+ * [RecyclerView]. The view inflation will happen on background thread and inflated [ViewHolder]s
+ * will be added to [RecycledViewPool] on main thread.
+ */
+class AllAppsRecyclerViewPool<T> : RecycledViewPool() {
+
+    private var future: Future<Void>? = null
+
+    /**
+     * Preinflate app icons. If all apps RV cannot be scrolled down, we don't need to preinflate.
+     */
+    fun <T> preInflateAllAppsViewHolders(context: T) where T : Context, T : ActivityContext {
+        val appsView = context.appsView ?: return
+        val activeRv: RecyclerView = appsView.activeRecyclerView ?: return
+        val preInflateCount = getPreinflateCount(context)
+        if (preInflateCount <= 0) {
+            return
+        }
+
+        // Because we perform onCreateViewHolder() on worker thread, we need a separate
+        // adapter/inflator object as they are not thread-safe. Note that the adapter
+        // just need to perform onCreateViewHolder(parent, VIEW_TYPE_ICON) so it doesn't need
+        // data source information.
+        val adapter: RecyclerView.Adapter<BaseAllAppsAdapter.ViewHolder> =
+            object : BaseAllAppsAdapter<T>(context, context.appsView.layoutInflater, null, null) {
+                override fun setAppsPerRow(appsPerRow: Int) = Unit
+                override fun getLayoutManager(): RecyclerView.LayoutManager? = null
+            }
+
+        // Inflate view holders on background thread, and added to view pool on main thread.
+        future?.cancel(true)
+        future =
+            VIEW_PREINFLATION_EXECUTOR.submit<Void> {
+                val viewHolders =
+                    Array(preInflateCount) {
+                        adapter.createViewHolder(activeRv, BaseAllAppsAdapter.VIEW_TYPE_ICON)
+                    }
+                MAIN_EXECUTOR.execute {
+                    for (i in 0 until minOf(viewHolders.size, getPreinflateCount(context))) {
+                        putRecycledView(viewHolders[i])
+                    }
+                }
+                null
+            }
+    }
+
+    /**
+     * After testing on phone, foldable and tablet, we found [PREINFLATE_ICONS_ROW_COUNT] rows of
+     * app icons plus [EXTRA_ICONS_COUNT] is the magic minimal count of app icons to preinflate to
+     * suffice fast scrolling.
+     */
+    fun <T> getPreinflateCount(context: T): Int where T : Context, T : ActivityContext {
+        val targetPreinflateCount =
+            PREINFLATE_ICONS_ROW_COUNT * context.deviceProfile.numShownAllAppsColumns +
+                EXTRA_ICONS_COUNT
+        val existingPreinflateCount = getRecycledViewCount(BaseAllAppsAdapter.VIEW_TYPE_ICON)
+        return targetPreinflateCount - existingPreinflateCount
+    }
+}
diff --git a/src/com/android/launcher3/secondarydisplay/PinnedAppsAdapter.java b/src/com/android/launcher3/secondarydisplay/PinnedAppsAdapter.java
index f03c62a..2d69bfa 100644
--- a/src/com/android/launcher3/secondarydisplay/PinnedAppsAdapter.java
+++ b/src/com/android/launcher3/secondarydisplay/PinnedAppsAdapter.java
@@ -60,13 +60,15 @@
     private final OnClickListener mOnClickListener;
     private final OnLongClickListener mOnLongClickListener;
     private final SharedPreferences mPrefs;
-    private final AllAppsStore mAllAppsList;
+    private final AllAppsStore<SecondaryDisplayLauncher> mAllAppsList;
     private final AppInfoComparator mAppNameComparator;
 
     private final Set<ComponentKey> mPinnedApps = new HashSet<>();
     private final ArrayList<AppInfo> mItems = new ArrayList<>();
 
-    public PinnedAppsAdapter(SecondaryDisplayLauncher launcher, AllAppsStore allAppsStore,
+    public PinnedAppsAdapter(
+            SecondaryDisplayLauncher launcher,
+            AllAppsStore<SecondaryDisplayLauncher> allAppsStore,
             OnLongClickListener onLongClickListener) {
         mLauncher = launcher;
         mOnClickListener = launcher.getItemOnClickListener();
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
index 458f137..a10c0ad 100644
--- a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
@@ -72,7 +72,7 @@
         implements BgDataModel.Callbacks, DragController.DragListener {
 
     private LauncherModel mModel;
-    private BaseDragLayer mDragLayer;
+    private SecondaryDragLayer mDragLayer;
     private SecondaryDragController mDragController;
     private ActivityAllAppsContainerView<SecondaryDisplayLauncher> mAppsView;
     private View mAppsButton;
@@ -302,7 +302,7 @@
     public void bindAllApplications(AppInfo[] apps, int flags,
             Map<PackageUserKey, Integer> packageUserKeytoUidMap) {
         Preconditions.assertUIThread();
-        AllAppsStore appsStore = mAppsView.getAppsStore();
+        AllAppsStore<SecondaryDisplayLauncher> appsStore = mAppsView.getAppsStore();
         appsStore.setApps(apps, flags, packageUserKeytoUidMap);
         PopupContainerWithArrow.dismissInvalidPopup(this);
     }
@@ -314,10 +314,6 @@
         }
     }
 
-    public SecondaryDisplayPredictions getSecondaryDisplayPredictions() {
-        return mSecondaryDisplayPredictions;
-    }
-
     @Override
     public StringCache getStringCache() {
         return mStringCache;
@@ -337,6 +333,11 @@
         return this::onIconClicked;
     }
 
+    @Override
+    public View.OnLongClickListener getAllAppsItemLongClickListener() {
+        return v -> mDragLayer.onIconLongClicked(v);
+    }
+
     private void onIconClicked(View v) {
         // Make sure that rogue clicks don't get through while allapps is launching, or after the
         // view has detached (it's possible for this to happen if the view is removed mid touch).
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayPredictions.java b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayPredictions.java
index 21c50d3..a58916a 100644
--- a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayPredictions.java
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayPredictions.java
@@ -16,10 +16,8 @@
 package com.android.launcher3.secondarydisplay;
 
 import android.content.Context;
-import android.view.View;
 
 import com.android.launcher3.R;
-import com.android.launcher3.allapps.ActivityAllAppsContainerView;
 import com.android.launcher3.model.BgDataModel;
 import com.android.launcher3.util.ResourceBasedOverride;
 
@@ -47,12 +45,4 @@
      */
     public void setPredictedApps(BgDataModel.FixedContainerItems item) {
     }
-
-    /**
-     * Set long click listener for predicted apps in top of app drawer.
-     */
-    public void setLongClickListener(
-            ActivityAllAppsContainerView<?> appsView,
-            View.OnLongClickListener onIconLongClickListener) {
-    }
 }
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java b/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java
index 87afcab..717164e 100644
--- a/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java
@@ -79,9 +79,6 @@
         mAllAppsButton = findViewById(R.id.all_apps_button);
 
         mAppsView = findViewById(R.id.apps_view);
-        mAppsView.setOnIconLongClickListener(this::onIconLongClicked);
-        mActivity.getSecondaryDisplayPredictions()
-                .setLongClickListener(mAppsView, this::onIconLongClicked);
         // Setup workspace
         mWorkspace = findViewById(R.id.workspace_grid);
         mPinnedAppsAdapter = new PinnedAppsAdapter(mActivity, mAppsView.getAppsStore(),
@@ -179,7 +176,7 @@
         return mPinnedAppsAdapter;
     }
 
-    private boolean onIconLongClicked(View v) {
+    boolean onIconLongClicked(View v) {
         if (!(v instanceof BubbleTextView)) {
             return false;
         }
diff --git a/src/com/android/launcher3/touch/AllAppsSwipeController.java b/src/com/android/launcher3/touch/AllAppsSwipeController.java
index ad812f0..447d22b 100644
--- a/src/com/android/launcher3/touch/AllAppsSwipeController.java
+++ b/src/com/android/launcher3/touch/AllAppsSwipeController.java
@@ -15,6 +15,7 @@
  */
 package com.android.launcher3.touch;
 
+import static com.android.app.animation.Interpolators.DECELERATED_EASE;
 import static com.android.app.animation.Interpolators.EMPHASIZED;
 import static com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE;
 import static com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE;
@@ -211,8 +212,8 @@
             if (!config.userControlled) {
                 config.setInterpolator(ANIM_VERTICAL_PROGRESS, EMPHASIZED);
             }
-            config.setInterpolator(ANIM_WORKSPACE_SCALE, EMPHASIZED);
-            config.setInterpolator(ANIM_DEPTH, EMPHASIZED);
+            config.setInterpolator(ANIM_WORKSPACE_SCALE, DECELERATED_EASE);
+            config.setInterpolator(ANIM_DEPTH, DECELERATED_EASE);
         } else {
             if (config.userControlled) {
                 config.setInterpolator(ANIM_DEPTH, Interpolators.reverse(BLUR_MANUAL));
@@ -252,8 +253,8 @@
             if (!config.userControlled) {
                 config.setInterpolator(ANIM_VERTICAL_PROGRESS, EMPHASIZED);
             }
-            config.setInterpolator(ANIM_WORKSPACE_SCALE, EMPHASIZED);
-            config.setInterpolator(ANIM_DEPTH, EMPHASIZED);
+            config.setInterpolator(ANIM_WORKSPACE_SCALE, DECELERATED_EASE);
+            config.setInterpolator(ANIM_DEPTH, DECELERATED_EASE);
         } else {
             config.setInterpolator(ANIM_DEPTH, config.userControlled ? BLUR_MANUAL : BLUR_ATOMIC);
             config.setInterpolator(ANIM_WORKSPACE_FADE,
diff --git a/src/com/android/launcher3/util/DimensionUtils.kt b/src/com/android/launcher3/util/DimensionUtils.kt
index 9188c2e..0eb0e08 100644
--- a/src/com/android/launcher3/util/DimensionUtils.kt
+++ b/src/com/android/launcher3/util/DimensionUtils.kt
@@ -29,9 +29,9 @@
      */
     @JvmStatic
     fun getTaskbarPhoneDimensions(
-        deviceProfile: DeviceProfile,
-        res: Resources,
-        isPhoneMode: Boolean
+            deviceProfile: DeviceProfile,
+            res: Resources,
+            isPhoneMode: Boolean
     ): Point {
         val p = Point()
         // Taskbar for large screen
diff --git a/src/com/android/launcher3/util/Executors.java b/src/com/android/launcher3/util/Executors.java
index 6978e0c..dec4b5c 100644
--- a/src/com/android/launcher3/util/Executors.java
+++ b/src/com/android/launcher3/util/Executors.java
@@ -21,6 +21,7 @@
 
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.ThreadPoolExecutor;
@@ -58,6 +59,11 @@
             new LooperExecutor(
                     createAndStartNewLooper("UiThreadHelper", Process.THREAD_PRIORITY_FOREGROUND));
 
+
+    /** A background executor to preinflate views. */
+    public static final ExecutorService VIEW_PREINFLATION_EXECUTOR =
+            java.util.concurrent.Executors.newSingleThreadExecutor();
+
     /**
      * Utility method to get a started handler thread statically
      */
diff --git a/quickstep/src/com/android/launcher3/proxy/StartActivityParams.java b/src/com/android/launcher3/util/StartActivityParams.java
similarity index 94%
rename from quickstep/src/com/android/launcher3/proxy/StartActivityParams.java
rename to src/com/android/launcher3/util/StartActivityParams.java
index 4d0bee6..b48562f 100644
--- a/quickstep/src/com/android/launcher3/proxy/StartActivityParams.java
+++ b/src/com/android/launcher3/util/StartActivityParams.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.launcher3.proxy;
+package com.android.launcher3.util;
 
 import static android.app.PendingIntent.FLAG_MUTABLE;
 import static android.app.PendingIntent.FLAG_ONE_SHOT;
@@ -34,6 +34,9 @@
 import android.os.Parcelable;
 import android.util.Log;
 
+/**
+ * Wrapper class for parameters to start an activity.
+ */
 public class StartActivityParams implements Parcelable {
 
     private static final String TAG = "StartActivityParams";
@@ -93,6 +96,7 @@
         parcel.writeBundle(options);
     }
 
+    /** Perform the operation on the pendingIntent. */
     public void deliverResult(Context context, int resultCode, Intent data) {
         ActivityOptions options = allowBGLaunch(ActivityOptions.makeBasic());
         try {
@@ -105,7 +109,7 @@
     }
 
     public static final Parcelable.Creator<StartActivityParams> CREATOR =
-            new Parcelable.Creator<StartActivityParams>() {
+            new Parcelable.Creator<>() {
                 public StartActivityParams createFromParcel(Parcel source) {
                     return new StartActivityParams(source);
                 }
diff --git a/src/com/android/launcher3/views/ActivityContext.java b/src/com/android/launcher3/views/ActivityContext.java
index d04f5e2..84ea871 100644
--- a/src/com/android/launcher3/views/ActivityContext.java
+++ b/src/com/android/launcher3/views/ActivityContext.java
@@ -239,6 +239,11 @@
         };
     }
 
+    /** Long-click callback used for All Apps items. */
+    default View.OnLongClickListener getAllAppsItemLongClickListener() {
+        return v -> false;
+    }
+
     @Nullable
     default PopupDataProvider getPopupDataProvider() {
         return null;
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index a3ef6e1..c9cd4b6 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -891,6 +891,19 @@
         return false;
     }
 
+    /** Gets the sheet for widget picker, which is used for testing. */
+    @VisibleForTesting
+    public View getSheet() {
+        return mContent;
+    }
+
+    /** Opens the first header in widget picker and scrolls to the top of the RecyclerView. */
+    @VisibleForTesting
+    public void openFirstHeader() {
+        mAdapters.get(AdapterHolder.PRIMARY).mWidgetsListAdapter.selectFirstHeaderEntry();
+        mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView.scrollToTop();
+    }
+
     /** A holder class for holding adapters & their corresponding recycler view. */
     final class AdapterHolder {
         static final int PRIMARY = 0;
diff --git a/tests/Android.bp b/tests/Android.bp
index d518a0e..e7f4084 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -58,7 +58,6 @@
       "src/com/android/launcher3/util/rule/SimpleActivityRule.java",
       "src/com/android/launcher3/util/rule/TestStabilityRule.java",
       "src/com/android/launcher3/util/rule/TISBindRule.java",
-      "src/com/android/launcher3/util/rule/ViewCaptureAnalysisRule.java",
       "src/com/android/launcher3/testcomponent/BaseTestingActivity.java",
       "src/com/android/launcher3/testcomponent/OtherBaseTestingActivity.java",
       "src/com/android/launcher3/testcomponent/CustomShortcutConfigActivity.java",
diff --git a/tests/AndroidManifest-common.xml b/tests/AndroidManifest-common.xml
index b170061..c8b5a20 100644
--- a/tests/AndroidManifest-common.xml
+++ b/tests/AndroidManifest-common.xml
@@ -41,6 +41,17 @@
         </receiver>
 
         <receiver
+            android:name="com.android.launcher3.testcomponent.AppWidgetNoConfigLarge"
+            android:exported="true"
+            android:label="No Config Large">
+            <intent-filter>
+                <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
+            </intent-filter>
+            <meta-data android:name="android.appwidget.provider"
+                android:resource="@xml/appwidget_no_config_large"/>
+        </receiver>
+
+        <receiver
             android:name="com.android.launcher3.testcomponent.AppWdigetHidden"
             android:exported="true"
             android:label="Hidden widget">
diff --git a/tests/res/xml/appwidget_no_config_large.xml b/tests/res/xml/appwidget_no_config_large.xml
new file mode 100644
index 0000000..b3b69d9
--- /dev/null
+++ b/tests/res/xml/appwidget_no_config_large.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<appwidget-provider
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:minWidth="1dp"
+    android:minHeight="1dp"
+    android:minResizeWidth="1dp"
+    android:maxResizeWidth="3000dp"
+    android:targetCellHeight="1"
+    android:targetCellWidth="5"
+    android:updatePeriodMillis="86400000"
+    android:initialLayout="@layout/test_layout_appwidget_red"
+    android:previewLayout="@layout/test_layout_appwidget_red"
+    android:resizeMode="horizontal|vertical"
+    android:widgetCategory="home_screen">
+</appwidget-provider>
\ No newline at end of file
diff --git a/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java b/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
index 36e4e76..75cee2f 100644
--- a/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
+++ b/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
@@ -110,6 +110,7 @@
     public static final String REQUEST_GET_TEST_EVENTS = "get-test-events";
     public static final String REQUEST_GET_HAD_NONTEST_EVENTS = "get-had-nontest-events";
     public static final String REQUEST_STOP_EVENT_LOGGING = "stop-event-logging";
+    public static final String REQUEST_REINITIALIZE_DATA = "reinitialize-data";
     public static final String REQUEST_CLEAR_DATA = "clear-data";
     public static final String REQUEST_HOTSEAT_ICON_NAMES = "get-hotseat-icon-names";
     public static final String REQUEST_IS_TABLET = "is-tablet";
@@ -153,10 +154,10 @@
     public static final String REQUEST_MOCK_SENSOR_ROTATION = "mock-sensor-rotation";
 
     public static final String PERMANENT_DIAG_TAG = "TaplTarget";
-    public static final String WORK_TAB_MISSING = "b/243688989";
     public static final String TWO_TASKBAR_LONG_CLICKS = "b/262282528";
     public static final String FLAKY_ACTIVITY_COUNT = "b/260260325";
     public static final String ICON_MISSING = "b/282963545";
+    public static final String ACTIVITY_LIFECYCLE_RULE = "b/289161193";
 
     public static final String REQUEST_EMULATE_DISPLAY = "emulate-display";
     public static final String REQUEST_STOP_EMULATE_DISPLAY = "stop-emulate-display";
diff --git a/tests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java b/tests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java
index ee05fe6..91a0634 100644
--- a/tests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java
+++ b/tests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java
@@ -164,7 +164,7 @@
         Context c = new ActivityContextWrapper(getApplicationContext());
         DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(c).getDeviceProfile(c).copy(c);
         mPrevNumColumns = dp.inv.numColumns;
-        mPrevNumRows = dp.inv.numColumns;
+        mPrevNumRows = dp.inv.numRows;
     }
 
     @After
diff --git a/tests/src/com/android/launcher3/nonquickstep/DeviceProfileDumpTest.kt b/tests/src/com/android/launcher3/nonquickstep/DeviceProfileDumpTest.kt
index 0a95771..270672f 100644
--- a/tests/src/com/android/launcher3/nonquickstep/DeviceProfileDumpTest.kt
+++ b/tests/src/com/android/launcher3/nonquickstep/DeviceProfileDumpTest.kt
@@ -64,8 +64,8 @@
                     "\tinv.numColumns: 5\n" +
                     "\tinv.numSearchContainerColumns: 5\n" +
                     "\tminCellSize: PointF(0.0, 0.0)dp\n" +
-                    "\tcellWidthPx: 165.0px (62.857143dp)\n" +
-                    "\tcellHeightPx: 235.0px (89.52381dp)\n" +
+                    "\tcellWidthPx: 159.0px (60.57143dp)\n" +
+                    "\tcellHeightPx: 229.0px (87.2381dp)\n" +
                     "\tgetCellSize().x: 207.0px (78.85714dp)\n" +
                     "\tgetCellSize().y: 379.0px (144.38095dp)\n" +
                     "\tcellLayoutBorderSpacePx Horizontal: 0.0px (0.0dp)\n" +
@@ -76,14 +76,14 @@
                     "\tcellLayoutPaddingPx.bottom: 28.0px (10.666667dp)\n" +
                     "\ticonSizePx: 147.0px (56.0dp)\n" +
                     "\ticonTextSizePx: 38.0px (14.476191dp)\n" +
-                    "\ticonDrawablePaddingPx: 18.0px (6.857143dp)\n" +
+                    "\ticonDrawablePaddingPx: 12.0px (4.571429dp)\n" +
                     "\tinv.numFolderRows: 4\n" +
                     "\tinv.numFolderColumns: 4\n" +
                     "\tfolderCellWidthPx: 195.0px (74.28571dp)\n" +
                     "\tfolderCellHeightPx: 230.0px (87.61905dp)\n" +
                     "\tfolderChildIconSizePx: 147.0px (56.0dp)\n" +
                     "\tfolderChildTextSizePx: 38.0px (14.476191dp)\n" +
-                    "\tfolderChildDrawablePaddingPx: 10.0px (3.8095238dp)\n" +
+                    "\tfolderChildDrawablePaddingPx: 4.0px (1.5238096dp)\n" +
                     "\tfolderCellLayoutBorderSpacePx.x: 0.0px (0.0dp)\n" +
                     "\tfolderCellLayoutBorderSpacePx.y: 0.0px (0.0dp)\n" +
                     "\tfolderContentPaddingLeftRight: 21.0px (8.0dp)\n" +
@@ -139,8 +139,8 @@
                     "\tworkspacePadding.bottom: 203.0px (77.333336dp)\n" +
                     "\ticonScale: 1.0px (0.3809524dp)\n" +
                     "\tcellScaleToFit : 1.0px (0.3809524dp)\n" +
-                    "\textraSpace: 722.0px (275.0476dp)\n" +
-                    "\tunscaled extraSpace: 722.0px (275.0476dp)\n" +
+                    "\textraSpace: 752.0px (286.4762dp)\n" +
+                    "\tunscaled extraSpace: 752.0px (286.4762dp)\n" +
                     "\tmaxEmptySpace: 0.0px (0.0dp)\n" +
                     "\tworkspaceTopPadding: 0.0px (0.0dp)\n" +
                     "\tworkspaceBottomPadding: 0.0px (0.0dp)\n" +
@@ -201,8 +201,8 @@
                     "\tinv.numColumns: 5\n" +
                     "\tinv.numSearchContainerColumns: 5\n" +
                     "\tminCellSize: PointF(0.0, 0.0)dp\n" +
-                    "\tcellWidthPx: 165.0px (62.857143dp)\n" +
-                    "\tcellHeightPx: 235.0px (89.52381dp)\n" +
+                    "\tcellWidthPx: 159.0px (60.57143dp)\n" +
+                    "\tcellHeightPx: 229.0px (87.2381dp)\n" +
                     "\tgetCellSize().x: 207.0px (78.85714dp)\n" +
                     "\tgetCellSize().y: 383.0px (145.90475dp)\n" +
                     "\tcellLayoutBorderSpacePx Horizontal: 0.0px (0.0dp)\n" +
@@ -213,14 +213,14 @@
                     "\tcellLayoutPaddingPx.bottom: 28.0px (10.666667dp)\n" +
                     "\ticonSizePx: 147.0px (56.0dp)\n" +
                     "\ticonTextSizePx: 38.0px (14.476191dp)\n" +
-                    "\ticonDrawablePaddingPx: 18.0px (6.857143dp)\n" +
+                    "\ticonDrawablePaddingPx: 12.0px (4.571429dp)\n" +
                     "\tinv.numFolderRows: 4\n" +
                     "\tinv.numFolderColumns: 4\n" +
                     "\tfolderCellWidthPx: 195.0px (74.28571dp)\n" +
                     "\tfolderCellHeightPx: 230.0px (87.61905dp)\n" +
                     "\tfolderChildIconSizePx: 147.0px (56.0dp)\n" +
                     "\tfolderChildTextSizePx: 38.0px (14.476191dp)\n" +
-                    "\tfolderChildDrawablePaddingPx: 10.0px (3.8095238dp)\n" +
+                    "\tfolderChildDrawablePaddingPx: 4.0px (1.5238096dp)\n" +
                     "\tfolderCellLayoutBorderSpacePx.x: 0.0px (0.0dp)\n" +
                     "\tfolderCellLayoutBorderSpacePx.y: 0.0px (0.0dp)\n" +
                     "\tfolderContentPaddingLeftRight: 21.0px (8.0dp)\n" +
@@ -276,8 +276,8 @@
                     "\tworkspacePadding.bottom: 245.0px (93.333336dp)\n" +
                     "\ticonScale: 1.0px (0.3809524dp)\n" +
                     "\tcellScaleToFit : 1.0px (0.3809524dp)\n" +
-                    "\textraSpace: 743.0px (283.0476dp)\n" +
-                    "\tunscaled extraSpace: 743.0px (283.0476dp)\n" +
+                    "\textraSpace: 773.0px (294.4762dp)\n" +
+                    "\tunscaled extraSpace: 773.0px (294.4762dp)\n" +
                     "\tmaxEmptySpace: 0.0px (0.0dp)\n" +
                     "\tworkspaceTopPadding: 0.0px (0.0dp)\n" +
                     "\tworkspaceBottomPadding: 0.0px (0.0dp)\n" +
@@ -338,7 +338,7 @@
                     "\tinv.numColumns: 5\n" +
                     "\tinv.numSearchContainerColumns: 5\n" +
                     "\tminCellSize: PointF(0.0, 0.0)dp\n" +
-                    "\tcellWidthPx: 158.0px (60.190475dp)\n" +
+                    "\tcellWidthPx: 152.0px (57.904762dp)\n" +
                     "\tcellHeightPx: 166.0px (63.238094dp)\n" +
                     "\tgetCellSize().x: 368.0px (140.19048dp)\n" +
                     "\tgetCellSize().y: 193.0px (73.52381dp)\n" +
@@ -357,7 +357,7 @@
                     "\tfolderCellHeightPx: 205.0px (78.09524dp)\n" +
                     "\tfolderChildIconSizePx: 131.0px (49.904762dp)\n" +
                     "\tfolderChildTextSizePx: 34.0px (12.952381dp)\n" +
-                    "\tfolderChildDrawablePaddingPx: 9.0px (3.4285715dp)\n" +
+                    "\tfolderChildDrawablePaddingPx: 4.0px (1.5238096dp)\n" +
                     "\tfolderCellLayoutBorderSpacePx.x: 0.0px (0.0dp)\n" +
                     "\tfolderCellLayoutBorderSpacePx.y: 0.0px (0.0dp)\n" +
                     "\tfolderContentPaddingLeftRight: 21.0px (8.0dp)\n" +
@@ -475,7 +475,7 @@
                     "\tinv.numColumns: 5\n" +
                     "\tinv.numSearchContainerColumns: 5\n" +
                     "\tminCellSize: PointF(0.0, 0.0)dp\n" +
-                    "\tcellWidthPx: 158.0px (60.190475dp)\n" +
+                    "\tcellWidthPx: 152.0px (57.904762dp)\n" +
                     "\tcellHeightPx: 166.0px (63.238094dp)\n" +
                     "\tgetCellSize().x: 393.0px (149.71428dp)\n" +
                     "\tgetCellSize().y: 180.0px (68.57143dp)\n" +
@@ -494,7 +494,7 @@
                     "\tfolderCellHeightPx: 192.0px (73.14286dp)\n" +
                     "\tfolderChildIconSizePx: 123.0px (46.857143dp)\n" +
                     "\tfolderChildTextSizePx: 32.0px (12.190476dp)\n" +
-                    "\tfolderChildDrawablePaddingPx: 8.0px (3.047619dp)\n" +
+                    "\tfolderChildDrawablePaddingPx: 3.0px (1.1428572dp)\n" +
                     "\tfolderCellLayoutBorderSpacePx.x: 0.0px (0.0dp)\n" +
                     "\tfolderCellLayoutBorderSpacePx.y: 0.0px (0.0dp)\n" +
                     "\tfolderContentPaddingLeftRight: 21.0px (8.0dp)\n" +
@@ -625,14 +625,14 @@
                     "\tcellLayoutPaddingPx.bottom: 59.0px (29.5dp)\n" +
                     "\ticonSizePx: 120.0px (60.0dp)\n" +
                     "\ticonTextSizePx: 28.0px (14.0dp)\n" +
-                    "\ticonDrawablePaddingPx: 14.0px (7.0dp)\n" +
+                    "\ticonDrawablePaddingPx: 9.0px (4.5dp)\n" +
                     "\tinv.numFolderRows: 3\n" +
                     "\tinv.numFolderColumns: 3\n" +
                     "\tfolderCellWidthPx: 240.0px (120.0dp)\n" +
                     "\tfolderCellHeightPx: 208.0px (104.0dp)\n" +
                     "\tfolderChildIconSizePx: 120.0px (60.0dp)\n" +
                     "\tfolderChildTextSizePx: 28.0px (14.0dp)\n" +
-                    "\tfolderChildDrawablePaddingPx: 16.0px (8.0dp)\n" +
+                    "\tfolderChildDrawablePaddingPx: 11.0px (5.5dp)\n" +
                     "\tfolderCellLayoutBorderSpacePx.x: 0.0px (0.0dp)\n" +
                     "\tfolderCellLayoutBorderSpacePx.y: 0.0px (0.0dp)\n" +
                     "\tfolderContentPaddingLeftRight: 0.0px (0.0dp)\n" +
@@ -649,7 +649,7 @@
                     "\tallAppsCloseDuration: 500\n" +
                     "\tallAppsIconSizePx: 120.0px (60.0dp)\n" +
                     "\tallAppsIconTextSizePx: 28.0px (14.0dp)\n" +
-                    "\tallAppsIconDrawablePaddingPx: 14.0px (7.0dp)\n" +
+                    "\tallAppsIconDrawablePaddingPx: 9.0px (4.5dp)\n" +
                     "\tallAppsCellHeightPx: 284.0px (142.0dp)\n" +
                     "\tallAppsCellWidthPx: 252.0px (126.0dp)\n" +
                     "\tallAppsBorderSpacePxX: 32.0px (16.0dp)\n" +
@@ -763,14 +763,14 @@
                     "\tcellLayoutPaddingPx.bottom: 59.0px (29.5dp)\n" +
                     "\ticonSizePx: 120.0px (60.0dp)\n" +
                     "\ticonTextSizePx: 28.0px (14.0dp)\n" +
-                    "\ticonDrawablePaddingPx: 14.0px (7.0dp)\n" +
+                    "\ticonDrawablePaddingPx: 9.0px (4.5dp)\n" +
                     "\tinv.numFolderRows: 3\n" +
                     "\tinv.numFolderColumns: 3\n" +
                     "\tfolderCellWidthPx: 240.0px (120.0dp)\n" +
                     "\tfolderCellHeightPx: 208.0px (104.0dp)\n" +
                     "\tfolderChildIconSizePx: 120.0px (60.0dp)\n" +
                     "\tfolderChildTextSizePx: 28.0px (14.0dp)\n" +
-                    "\tfolderChildDrawablePaddingPx: 16.0px (8.0dp)\n" +
+                    "\tfolderChildDrawablePaddingPx: 11.0px (5.5dp)\n" +
                     "\tfolderCellLayoutBorderSpacePx.x: 0.0px (0.0dp)\n" +
                     "\tfolderCellLayoutBorderSpacePx.y: 0.0px (0.0dp)\n" +
                     "\tfolderContentPaddingLeftRight: 0.0px (0.0dp)\n" +
@@ -787,7 +787,7 @@
                     "\tallAppsCloseDuration: 500\n" +
                     "\tallAppsIconSizePx: 120.0px (60.0dp)\n" +
                     "\tallAppsIconTextSizePx: 28.0px (14.0dp)\n" +
-                    "\tallAppsIconDrawablePaddingPx: 14.0px (7.0dp)\n" +
+                    "\tallAppsIconDrawablePaddingPx: 9.0px (4.5dp)\n" +
                     "\tallAppsCellHeightPx: 284.0px (142.0dp)\n" +
                     "\tallAppsCellWidthPx: 252.0px (126.0dp)\n" +
                     "\tallAppsBorderSpacePxX: 32.0px (16.0dp)\n" +
@@ -901,14 +901,14 @@
                     "\tcellLayoutPaddingPx.bottom: 72.0px (36.0dp)\n" +
                     "\ticonSizePx: 120.0px (60.0dp)\n" +
                     "\ticonTextSizePx: 28.0px (14.0dp)\n" +
-                    "\ticonDrawablePaddingPx: 14.0px (7.0dp)\n" +
+                    "\ticonDrawablePaddingPx: 9.0px (4.5dp)\n" +
                     "\tinv.numFolderRows: 3\n" +
                     "\tinv.numFolderColumns: 3\n" +
                     "\tfolderCellWidthPx: 204.0px (102.0dp)\n" +
                     "\tfolderCellHeightPx: 240.0px (120.0dp)\n" +
                     "\tfolderChildIconSizePx: 120.0px (60.0dp)\n" +
                     "\tfolderChildTextSizePx: 28.0px (14.0dp)\n" +
-                    "\tfolderChildDrawablePaddingPx: 27.0px (13.5dp)\n" +
+                    "\tfolderChildDrawablePaddingPx: 22.0px (11.0dp)\n" +
                     "\tfolderCellLayoutBorderSpacePx.x: 0.0px (0.0dp)\n" +
                     "\tfolderCellLayoutBorderSpacePx.y: 0.0px (0.0dp)\n" +
                     "\tfolderContentPaddingLeftRight: 0.0px (0.0dp)\n" +
@@ -925,7 +925,7 @@
                     "\tallAppsCloseDuration: 500\n" +
                     "\tallAppsIconSizePx: 120.0px (60.0dp)\n" +
                     "\tallAppsIconTextSizePx: 28.0px (14.0dp)\n" +
-                    "\tallAppsIconDrawablePaddingPx: 14.0px (7.0dp)\n" +
+                    "\tallAppsIconDrawablePaddingPx: 9.0px (4.5dp)\n" +
                     "\tallAppsCellHeightPx: 316.0px (158.0dp)\n" +
                     "\tallAppsCellWidthPx: 192.0px (96.0dp)\n" +
                     "\tallAppsBorderSpacePxX: 16.0px (8.0dp)\n" +
@@ -1039,14 +1039,14 @@
                     "\tcellLayoutPaddingPx.bottom: 72.0px (36.0dp)\n" +
                     "\ticonSizePx: 120.0px (60.0dp)\n" +
                     "\ticonTextSizePx: 28.0px (14.0dp)\n" +
-                    "\ticonDrawablePaddingPx: 14.0px (7.0dp)\n" +
+                    "\ticonDrawablePaddingPx: 9.0px (4.5dp)\n" +
                     "\tinv.numFolderRows: 3\n" +
                     "\tinv.numFolderColumns: 3\n" +
                     "\tfolderCellWidthPx: 204.0px (102.0dp)\n" +
                     "\tfolderCellHeightPx: 240.0px (120.0dp)\n" +
                     "\tfolderChildIconSizePx: 120.0px (60.0dp)\n" +
                     "\tfolderChildTextSizePx: 28.0px (14.0dp)\n" +
-                    "\tfolderChildDrawablePaddingPx: 27.0px (13.5dp)\n" +
+                    "\tfolderChildDrawablePaddingPx: 22.0px (11.0dp)\n" +
                     "\tfolderCellLayoutBorderSpacePx.x: 0.0px (0.0dp)\n" +
                     "\tfolderCellLayoutBorderSpacePx.y: 0.0px (0.0dp)\n" +
                     "\tfolderContentPaddingLeftRight: 0.0px (0.0dp)\n" +
@@ -1063,7 +1063,7 @@
                     "\tallAppsCloseDuration: 500\n" +
                     "\tallAppsIconSizePx: 120.0px (60.0dp)\n" +
                     "\tallAppsIconTextSizePx: 28.0px (14.0dp)\n" +
-                    "\tallAppsIconDrawablePaddingPx: 14.0px (7.0dp)\n" +
+                    "\tallAppsIconDrawablePaddingPx: 9.0px (4.5dp)\n" +
                     "\tallAppsCellHeightPx: 316.0px (158.0dp)\n" +
                     "\tallAppsCellWidthPx: 192.0px (96.0dp)\n" +
                     "\tallAppsBorderSpacePxX: 16.0px (8.0dp)\n" +
@@ -1170,8 +1170,8 @@
                     "\tinv.numColumns: 4\n" +
                     "\tinv.numSearchContainerColumns: 4\n" +
                     "\tminCellSize: PointF(0.0, 0.0)dp\n" +
-                    "\tcellWidthPx: 159.0px (60.57143dp)\n" +
-                    "\tcellHeightPx: 223.0px (84.95238dp)\n" +
+                    "\tcellWidthPx: 154.0px (58.666668dp)\n" +
+                    "\tcellHeightPx: 218.0px (83.04762dp)\n" +
                     "\tgetCellSize().x: 270.0px (102.85714dp)\n" +
                     "\tgetCellSize().y: 342.0px (130.28572dp)\n" +
                     "\tcellLayoutBorderSpacePx Horizontal: 0.0px (0.0dp)\n" +
@@ -1182,14 +1182,14 @@
                     "\tcellLayoutPaddingPx.bottom: 0.0px (0.0dp)\n" +
                     "\ticonSizePx: 141.0px (53.714287dp)\n" +
                     "\ticonTextSizePx: 34.0px (12.952381dp)\n" +
-                    "\ticonDrawablePaddingPx: 18.0px (6.857143dp)\n" +
+                    "\ticonDrawablePaddingPx: 13.0px (4.952381dp)\n" +
                     "\tinv.numFolderRows: 3\n" +
                     "\tinv.numFolderColumns: 4\n" +
                     "\tfolderCellWidthPx: 189.0px (72.0dp)\n" +
                     "\tfolderCellHeightPx: 219.0px (83.42857dp)\n" +
                     "\tfolderChildIconSizePx: 141.0px (53.714287dp)\n" +
                     "\tfolderChildTextSizePx: 34.0px (12.952381dp)\n" +
-                    "\tfolderChildDrawablePaddingPx: 10.0px (3.8095238dp)\n" +
+                    "\tfolderChildDrawablePaddingPx: 5.0px (1.9047619dp)\n" +
                     "\tfolderCellLayoutBorderSpacePx.x: 0.0px (0.0dp)\n" +
                     "\tfolderCellLayoutBorderSpacePx.y: 0.0px (0.0dp)\n" +
                     "\tfolderContentPaddingLeftRight: 21.0px (8.0dp)\n" +
@@ -1245,8 +1245,8 @@
                     "\tworkspacePadding.bottom: 330.0px (125.71429dp)\n" +
                     "\ticonScale: 1.0px (0.3809524dp)\n" +
                     "\tcellScaleToFit : 1.0px (0.3809524dp)\n" +
-                    "\textraSpace: 478.0px (182.09525dp)\n" +
-                    "\tunscaled extraSpace: 478.0px (182.09525dp)\n" +
+                    "\textraSpace: 498.0px (189.71428dp)\n" +
+                    "\tunscaled extraSpace: 498.0px (189.71428dp)\n" +
                     "\tmaxEmptySpace: 0.0px (0.0dp)\n" +
                     "\tworkspaceTopPadding: 0.0px (0.0dp)\n" +
                     "\tworkspaceBottomPadding: 0.0px (0.0dp)\n" +
@@ -1312,8 +1312,8 @@
                     "\tinv.numColumns: 4\n" +
                     "\tinv.numSearchContainerColumns: 4\n" +
                     "\tminCellSize: PointF(0.0, 0.0)dp\n" +
-                    "\tcellWidthPx: 159.0px (60.57143dp)\n" +
-                    "\tcellHeightPx: 223.0px (84.95238dp)\n" +
+                    "\tcellWidthPx: 154.0px (58.666668dp)\n" +
+                    "\tcellHeightPx: 218.0px (83.04762dp)\n" +
                     "\tgetCellSize().x: 270.0px (102.85714dp)\n" +
                     "\tgetCellSize().y: 342.0px (130.28572dp)\n" +
                     "\tcellLayoutBorderSpacePx Horizontal: 0.0px (0.0dp)\n" +
@@ -1324,14 +1324,14 @@
                     "\tcellLayoutPaddingPx.bottom: 0.0px (0.0dp)\n" +
                     "\ticonSizePx: 141.0px (53.714287dp)\n" +
                     "\ticonTextSizePx: 34.0px (12.952381dp)\n" +
-                    "\ticonDrawablePaddingPx: 18.0px (6.857143dp)\n" +
+                    "\ticonDrawablePaddingPx: 13.0px (4.952381dp)\n" +
                     "\tinv.numFolderRows: 3\n" +
                     "\tinv.numFolderColumns: 4\n" +
                     "\tfolderCellWidthPx: 189.0px (72.0dp)\n" +
                     "\tfolderCellHeightPx: 219.0px (83.42857dp)\n" +
                     "\tfolderChildIconSizePx: 141.0px (53.714287dp)\n" +
                     "\tfolderChildTextSizePx: 34.0px (12.952381dp)\n" +
-                    "\tfolderChildDrawablePaddingPx: 10.0px (3.8095238dp)\n" +
+                    "\tfolderChildDrawablePaddingPx: 5.0px (1.9047619dp)\n" +
                     "\tfolderCellLayoutBorderSpacePx.x: 0.0px (0.0dp)\n" +
                     "\tfolderCellLayoutBorderSpacePx.y: 0.0px (0.0dp)\n" +
                     "\tfolderContentPaddingLeftRight: 21.0px (8.0dp)\n" +
@@ -1387,8 +1387,8 @@
                     "\tworkspacePadding.bottom: 330.0px (125.71429dp)\n" +
                     "\ticonScale: 1.0px (0.3809524dp)\n" +
                     "\tcellScaleToFit : 1.0px (0.3809524dp)\n" +
-                    "\textraSpace: 478.0px (182.09525dp)\n" +
-                    "\tunscaled extraSpace: 478.0px (182.09525dp)\n" +
+                    "\textraSpace: 498.0px (189.71428dp)\n" +
+                    "\tunscaled extraSpace: 498.0px (189.71428dp)\n" +
                     "\tmaxEmptySpace: 0.0px (0.0dp)\n" +
                     "\tworkspaceTopPadding: 0.0px (0.0dp)\n" +
                     "\tworkspaceBottomPadding: 0.0px (0.0dp)\n" +
@@ -1454,8 +1454,8 @@
                     "\tinv.numColumns: 4\n" +
                     "\tinv.numSearchContainerColumns: 4\n" +
                     "\tminCellSize: PointF(0.0, 0.0)dp\n" +
-                    "\tcellWidthPx: 159.0px (60.57143dp)\n" +
-                    "\tcellHeightPx: 223.0px (84.95238dp)\n" +
+                    "\tcellWidthPx: 154.0px (58.666668dp)\n" +
+                    "\tcellHeightPx: 218.0px (83.04762dp)\n" +
                     "\tgetCellSize().x: 224.0px (85.333336dp)\n" +
                     "\tgetCellSize().y: 430.0px (163.80952dp)\n" +
                     "\tcellLayoutBorderSpacePx Horizontal: 0.0px (0.0dp)\n" +
@@ -1466,14 +1466,14 @@
                     "\tcellLayoutPaddingPx.bottom: 0.0px (0.0dp)\n" +
                     "\ticonSizePx: 141.0px (53.714287dp)\n" +
                     "\ticonTextSizePx: 34.0px (12.952381dp)\n" +
-                    "\ticonDrawablePaddingPx: 18.0px (6.857143dp)\n" +
+                    "\ticonDrawablePaddingPx: 13.0px (4.952381dp)\n" +
                     "\tinv.numFolderRows: 3\n" +
                     "\tinv.numFolderColumns: 4\n" +
                     "\tfolderCellWidthPx: 189.0px (72.0dp)\n" +
                     "\tfolderCellHeightPx: 219.0px (83.42857dp)\n" +
                     "\tfolderChildIconSizePx: 141.0px (53.714287dp)\n" +
                     "\tfolderChildTextSizePx: 34.0px (12.952381dp)\n" +
-                    "\tfolderChildDrawablePaddingPx: 10.0px (3.8095238dp)\n" +
+                    "\tfolderChildDrawablePaddingPx: 5.0px (1.9047619dp)\n" +
                     "\tfolderCellLayoutBorderSpacePx.x: 0.0px (0.0dp)\n" +
                     "\tfolderCellLayoutBorderSpacePx.y: 0.0px (0.0dp)\n" +
                     "\tfolderContentPaddingLeftRight: 21.0px (8.0dp)\n" +
@@ -1529,8 +1529,8 @@
                     "\tworkspacePadding.bottom: 330.0px (125.71429dp)\n" +
                     "\ticonScale: 1.0px (0.3809524dp)\n" +
                     "\tcellScaleToFit : 1.0px (0.3809524dp)\n" +
-                    "\textraSpace: 829.0px (315.8095dp)\n" +
-                    "\tunscaled extraSpace: 829.0px (315.8095dp)\n" +
+                    "\textraSpace: 849.0px (323.42856dp)\n" +
+                    "\tunscaled extraSpace: 849.0px (323.42856dp)\n" +
                     "\tmaxEmptySpace: 0.0px (0.0dp)\n" +
                     "\tworkspaceTopPadding: 0.0px (0.0dp)\n" +
                     "\tworkspaceBottomPadding: 0.0px (0.0dp)\n" +
@@ -1592,8 +1592,8 @@
                     "\tinv.numColumns: 4\n" +
                     "\tinv.numSearchContainerColumns: 4\n" +
                     "\tminCellSize: PointF(0.0, 0.0)dp\n" +
-                    "\tcellWidthPx: 159.0px (60.57143dp)\n" +
-                    "\tcellHeightPx: 223.0px (84.95238dp)\n" +
+                    "\tcellWidthPx: 154.0px (58.666668dp)\n" +
+                    "\tcellHeightPx: 218.0px (83.04762dp)\n" +
                     "\tgetCellSize().x: 224.0px (85.333336dp)\n" +
                     "\tgetCellSize().y: 430.0px (163.80952dp)\n" +
                     "\tcellLayoutBorderSpacePx Horizontal: 0.0px (0.0dp)\n" +
@@ -1604,14 +1604,14 @@
                     "\tcellLayoutPaddingPx.bottom: 0.0px (0.0dp)\n" +
                     "\ticonSizePx: 141.0px (53.714287dp)\n" +
                     "\ticonTextSizePx: 34.0px (12.952381dp)\n" +
-                    "\ticonDrawablePaddingPx: 18.0px (6.857143dp)\n" +
+                    "\ticonDrawablePaddingPx: 13.0px (4.952381dp)\n" +
                     "\tinv.numFolderRows: 3\n" +
                     "\tinv.numFolderColumns: 4\n" +
                     "\tfolderCellWidthPx: 189.0px (72.0dp)\n" +
                     "\tfolderCellHeightPx: 219.0px (83.42857dp)\n" +
                     "\tfolderChildIconSizePx: 141.0px (53.714287dp)\n" +
                     "\tfolderChildTextSizePx: 34.0px (12.952381dp)\n" +
-                    "\tfolderChildDrawablePaddingPx: 10.0px (3.8095238dp)\n" +
+                    "\tfolderChildDrawablePaddingPx: 5.0px (1.9047619dp)\n" +
                     "\tfolderCellLayoutBorderSpacePx.x: 0.0px (0.0dp)\n" +
                     "\tfolderCellLayoutBorderSpacePx.y: 0.0px (0.0dp)\n" +
                     "\tfolderContentPaddingLeftRight: 21.0px (8.0dp)\n" +
@@ -1667,8 +1667,8 @@
                     "\tworkspacePadding.bottom: 330.0px (125.71429dp)\n" +
                     "\ticonScale: 1.0px (0.3809524dp)\n" +
                     "\tcellScaleToFit : 1.0px (0.3809524dp)\n" +
-                    "\textraSpace: 829.0px (315.8095dp)\n" +
-                    "\tunscaled extraSpace: 829.0px (315.8095dp)\n" +
+                    "\textraSpace: 849.0px (323.42856dp)\n" +
+                    "\tunscaled extraSpace: 849.0px (323.42856dp)\n" +
                     "\tmaxEmptySpace: 0.0px (0.0dp)\n" +
                     "\tworkspaceTopPadding: 0.0px (0.0dp)\n" +
                     "\tworkspaceBottomPadding: 0.0px (0.0dp)\n" +
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 7f796e7..a82b005 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -70,7 +70,6 @@
 import com.android.launcher3.util.rule.ScreenRecordRule;
 import com.android.launcher3.util.rule.ShellCommandRule;
 import com.android.launcher3.util.rule.TestStabilityRule;
-import com.android.launcher3.util.rule.ViewCaptureAnalysisRule;
 import com.android.launcher3.util.rule.ViewCaptureRule;
 
 import org.junit.After;
@@ -209,8 +208,7 @@
         final RuleChain inner = RuleChain
                 .outerRule(new PortraitLandscapeRunner(this))
                 .around(new FailureWatcher(mLauncher, viewCaptureRule::getViewCaptureData))
-                .around(viewCaptureRule)
-                .around(new ViewCaptureAnalysisRule(viewCaptureRule.getViewCapture()));
+                .around(viewCaptureRule);
 
         return TestHelpers.isInLauncherProcess()
                 ? RuleChain.outerRule(ShellCommandRule.setDefaultLauncher()).around(inner)
@@ -293,8 +291,16 @@
         }
     }
 
-    protected void clearLauncherData() {
-        mLauncher.clearLauncherData();
+    protected void reinitializeLauncherData() {
+        reinitializeLauncherData(false);
+    }
+
+    protected void reinitializeLauncherData(boolean clearWorkspace) {
+        if (clearWorkspace) {
+            mLauncher.clearLauncherData();
+        } else {
+            mLauncher.reinitializeLauncherData();
+        }
         mLauncher.waitForLauncherInitialized();
     }
 
diff --git a/tests/src/com/android/launcher3/ui/BubbleTextViewTest.java b/tests/src/com/android/launcher3/ui/BubbleTextViewTest.java
index fdba4eb..ba17fdc 100644
--- a/tests/src/com/android/launcher3/ui/BubbleTextViewTest.java
+++ b/tests/src/com/android/launcher3/ui/BubbleTextViewTest.java
@@ -18,6 +18,8 @@
 
 import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
 
+import static com.android.launcher3.BubbleTextView.DISPLAY_ALL_APPS;
+import static com.android.launcher3.BubbleTextView.DISPLAY_PREDICTION_ROW;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_TWOLINE_ALLAPPS;
 
 import static org.junit.Assert.assertEquals;
@@ -79,7 +81,6 @@
         mContext = new ActivityContextWrapper(getApplicationContext());
         mBubbleTextView = new BubbleTextView(mContext);
         mBubbleTextView.reset();
-        mBubbleTextView.setDisplayAllApps();
 
         BubbleTextView testView = new BubbleTextView(mContext);
         testView.setTypeface(Typeface.MONOSPACE);
@@ -104,6 +105,7 @@
     public void testEmptyString_flagOn() {
         try (AutoCloseable flag = TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, true)) {
             mItemInfoWithIcon.title = EMPTY_STRING;
+            mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
             mBubbleTextView.applyLabel(mItemInfoWithIcon);
             mBubbleTextView.setTypeface(Typeface.MONOSPACE);
             mBubbleTextView.measure(mLimitedWidth, 0);
@@ -118,6 +120,7 @@
     public void testEmptyString_flagOff() {
         try (AutoCloseable flag = TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, false)) {
             mItemInfoWithIcon.title = EMPTY_STRING;
+            mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
             mBubbleTextView.applyLabel(mItemInfoWithIcon);
             mBubbleTextView.setTypeface(Typeface.MONOSPACE);
             mBubbleTextView.measure(mLimitedWidth, 0);
@@ -134,6 +137,7 @@
             // test string: "Battery Stats"
             mItemInfoWithIcon.title = TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT;
             mBubbleTextView.applyLabel(mItemInfoWithIcon);
+            mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
             mBubbleTextView.setTypeface(Typeface.MONOSPACE);
             mBubbleTextView.measure(mLimitedWidth, 0);
             mBubbleTextView.onPreDraw();
@@ -149,6 +153,7 @@
             // test string: "Battery Stats"
             mItemInfoWithIcon.title = TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT;
             mBubbleTextView.applyLabel(mItemInfoWithIcon);
+            mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
             mBubbleTextView.setTypeface(Typeface.MONOSPACE);
             mBubbleTextView.measure(mLimitedWidth, 0);
             mBubbleTextView.onPreDraw();
@@ -164,6 +169,7 @@
             // test string: "flutterappflorafy"
             mItemInfoWithIcon.title = TEST_LONG_STRING_NO_SPACE_LONGER_THAN_CHAR_LIMIT;
             mBubbleTextView.applyLabel(mItemInfoWithIcon);
+            mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
             mBubbleTextView.setTypeface(Typeface.MONOSPACE);
             mBubbleTextView.measure(mLimitedWidth, 0);
             mBubbleTextView.onPreDraw();
@@ -179,6 +185,7 @@
             // test string: "flutterappflorafy"
             mItemInfoWithIcon.title = TEST_LONG_STRING_NO_SPACE_LONGER_THAN_CHAR_LIMIT;
             mBubbleTextView.applyLabel(mItemInfoWithIcon);
+            mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
             mBubbleTextView.setTypeface(Typeface.MONOSPACE);
             mBubbleTextView.measure(mLimitedWidth, 0);
             mBubbleTextView.onPreDraw();
@@ -194,6 +201,7 @@
             // test string: "System UWB Field Test"
             mItemInfoWithIcon.title = TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT;
             mBubbleTextView.applyLabel(mItemInfoWithIcon);
+            mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
             mBubbleTextView.setTypeface(Typeface.MONOSPACE);
             mBubbleTextView.measure(mLimitedWidth, 0);
             mBubbleTextView.onPreDraw();
@@ -209,6 +217,7 @@
             // test string: "System UWB Field Test"
             mItemInfoWithIcon.title = TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT;
             mBubbleTextView.applyLabel(mItemInfoWithIcon);
+            mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
             mBubbleTextView.setTypeface(Typeface.MONOSPACE);
             mBubbleTextView.measure(mLimitedWidth, 0);
             mBubbleTextView.onPreDraw();
@@ -224,6 +233,7 @@
             // test string: "LEGO®Builder"
             mItemInfoWithIcon.title = TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT;
             mBubbleTextView.applyLabel(mItemInfoWithIcon);
+            mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
             mBubbleTextView.setTypeface(Typeface.MONOSPACE);
             mBubbleTextView.measure(mLimitedWidth, 0);
             mBubbleTextView.onPreDraw();
@@ -239,6 +249,7 @@
             // test string: "LEGO®Builder"
             mItemInfoWithIcon.title = TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT;
             mBubbleTextView.applyLabel(mItemInfoWithIcon);
+            mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
             mBubbleTextView.setTypeface(Typeface.MONOSPACE);
             mBubbleTextView.measure(mLimitedWidth, 0);
             mBubbleTextView.onPreDraw();
@@ -291,4 +302,20 @@
                 breakPoints);
         assertEquals(TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT_RESULT, newString);
     }
+
+    @Test
+    public void testEnsurePredictionRowIsOneLine() {
+        try (AutoCloseable flag = TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, true)) {
+            // test string: "Battery Stats"
+            mItemInfoWithIcon.title = TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT;
+            mBubbleTextView.setDisplay(DISPLAY_PREDICTION_ROW);
+            mBubbleTextView.applyLabel(mItemInfoWithIcon);
+            mBubbleTextView.setTypeface(Typeface.MONOSPACE);
+            mBubbleTextView.measure(mLimitedWidth, 0);
+            mBubbleTextView.onPreDraw();
+            assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
 }
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 2c8acc4..0d63a68 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -97,7 +97,12 @@
     }
 
     public static void initialize(AbstractLauncherUiTest test) throws Exception {
-        test.clearLauncherData();
+        initialize(test, false);
+    }
+
+    public static void initialize(
+            AbstractLauncherUiTest test, boolean clearWorkspace) throws Exception {
+        test.reinitializeLauncherData(clearWorkspace);
         test.mDevice.pressHome();
         test.waitForLauncherCondition("Launcher didn't start", launcher -> launcher != null);
         test.waitForState("Launcher internal state didn't switch to Home",
@@ -248,7 +253,7 @@
         LauncherLayoutBuilder builder = new LauncherLayoutBuilder()
                 .atHotseat(0).putApp("com.android.chrome", "com.google.android.apps.chrome.Main");
         mLauncherLayout = TestUtil.setLauncherDefaultLayout(mTargetContext, builder);
-        clearLauncherData();
+        reinitializeLauncherData();
 
         final Workspace workspace = mLauncher.getWorkspace();
 
@@ -525,7 +530,6 @@
     }
 
     @Test
-    @ScreenRecord // b/258071914
     @PortraitLandscape
     @PlatinumTest(focusArea = "launcher")
     public void testUninstallFromAllApps() throws Exception {
@@ -557,7 +561,7 @@
                 allApps.unfreeze();
             }
             // Reset the workspace for the next shortcut creation.
-            initialize(this);
+            initialize(this, true);
             endTime = SystemClock.uptimeMillis();
             elapsedTime = endTime - startTime;
             Log.d("testDragAppIconToWorkspaceCellTime",
diff --git a/tests/src/com/android/launcher3/ui/WorkProfileTest.java b/tests/src/com/android/launcher3/ui/WorkProfileTest.java
index 026766c..7237387 100644
--- a/tests/src/com/android/launcher3/ui/WorkProfileTest.java
+++ b/tests/src/com/android/launcher3/ui/WorkProfileTest.java
@@ -18,7 +18,6 @@
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.allapps.AllAppsStore.DEFER_UPDATES_TEST;
-import static com.android.launcher3.testing.shared.TestProtocol.WORK_TAB_MISSING;
 import static com.android.launcher3.util.TestUtil.installDummyAppForUser;
 import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
 import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
@@ -39,7 +38,6 @@
 import com.android.launcher3.allapps.WorkPausedCard;
 import com.android.launcher3.allapps.WorkProfileManager;
 import com.android.launcher3.tapl.LauncherInstrumentation;
-import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.util.TestUtil;
 import com.android.launcher3.util.rule.TestStabilityRule.Stability;
 
@@ -78,8 +76,6 @@
         installDummyAppForUser(mProfileUserId);
         updateWorkProfileSetupSuccessful("am start-user", output);
 
-        Log.d(WORK_TAB_MISSING, "workProfileSuccessful? " + mWorkProfileSetupSuccessful +
-                " shellCmd: " + logStr);
         if (!mWorkProfileSetupSuccessful) {
             return; // no need to setup launcher since all tests will skip.
         }
@@ -96,7 +92,6 @@
 
     @After
     public void removeWorkProfile() throws Exception {
-        Log.d(TestProtocol.WORK_TAB_MISSING, "WorkProfileTest teardown");
         executeOnLauncher(launcher -> {
             if (launcher == null || launcher.getAppsView() == null) {
                 return;
@@ -112,7 +107,6 @@
         mLauncher.getAllApps();
         waitForLauncherCondition("Work tab not setup", launcher -> {
             if (launcher.getAppsView().getContentView() instanceof AllAppsPagedView) {
-                Log.d(WORK_TAB_MISSING, "Deferring AppsStore updates");
                 launcher.getAppsView().getAppsStore().enableDeferUpdates(DEFER_UPDATES_TEST);
                 return true;
             }
diff --git a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
index 435649b..b05ebf8 100644
--- a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
@@ -21,6 +21,7 @@
 import static org.junit.Assert.assertTrue;
 
 import android.platform.test.annotations.PlatinumTest;
+import android.platform.test.rule.ScreenRecordRule;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.LargeTest;
@@ -53,7 +54,9 @@
     @PlatinumTest(focusArea = "launcher")
     @Test
     @PortraitLandscape
+    @ScreenRecordRule.ScreenRecord // b/289161193
     public void testDragIcon() throws Throwable {
+        mLauncher.enableDebugTracing(); // b/289161193
         new FavoriteItemsTransaction(mTargetContext).commitAndLoadHome(mLauncher);
 
         waitForLauncherCondition("Workspace didn't finish loading", l -> !l.isWorkspaceLoading());
@@ -79,6 +82,7 @@
                 DEFAULT_UI_TIMEOUT);
         assertNotNull("Widget not found on the workspace", widget);
         widget.launch(getAppPackageName());
+        mLauncher.disableDebugTracing(); // b/289161193
     }
 
     /**
diff --git a/tests/src/com/android/launcher3/util/rule/LazyActivityRule.java b/tests/src/com/android/launcher3/util/rule/LazyActivityRule.java
new file mode 100644
index 0000000..6c300bb
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/rule/LazyActivityRule.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.util.rule;
+
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+
+import androidx.annotation.Nullable;
+import androidx.test.core.app.ActivityScenario;
+
+import com.android.launcher3.Launcher;
+
+import org.junit.rules.ExternalResource;
+
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+/**
+ * Similar to {@code ActivityScenarioRule} but it creates the activity lazily when needed
+ */
+public class LazyActivityRule<A extends Activity> extends ExternalResource {
+
+    private final Supplier<ActivityScenario<A>> mScenarioSupplier;
+
+    @Nullable private ActivityScenario<A> mScenario;
+
+    /**
+     * Constructs LazyActivityScenarioRule for a given scenario provider.
+     */
+    public LazyActivityRule(Supplier<ActivityScenario<A>> supplier) {
+        mScenarioSupplier = supplier;
+    }
+
+    /**
+     * Resets the rule, such that the activity is in closed state
+     */
+    public synchronized void reset() {
+        if (mScenario != null) {
+            try {
+                mScenario.close();
+            } catch (AssertionError e) {
+                // Ignore errors during close
+            }
+        }
+        mScenario = null;
+    }
+
+    @Override
+    protected synchronized void after() {
+        reset();
+    }
+
+    /**
+     * Returns the scenario, creating one if it doesn't exist
+     */
+    public synchronized ActivityScenario<A> getScenario() {
+        if (mScenario == null) {
+            mScenario = mScenarioSupplier.get();
+        }
+        return mScenario;
+    }
+
+    /**
+     * Executes the function {@code f} on the activities main thread and returns the result
+     */
+    public <T> T getFromActivity(Function<A, T> f) {
+        AtomicReference<T> result = new AtomicReference<>();
+        getScenario().onActivity(a -> result.set(f.apply(a)));
+        return result.get();
+    }
+
+    /**
+     * Runs the provided function {@code f} on the activity if the scenario is already created
+     */
+    public synchronized void runOnActivity(Consumer<A> f) {
+        if (mScenario != null) {
+            mScenario.onActivity(f::accept);
+        }
+    }
+
+    /**
+     * Returns a {@link LazyActivityRule} for the Launcher activity
+     */
+    public static <T extends Launcher> LazyActivityRule<T> forLauncher() {
+        Context context = getInstrumentation().getTargetContext();
+        // Create the activity after the model setup is done.
+        Intent homeIntent = new Intent(Intent.ACTION_MAIN)
+                .addCategory(Intent.CATEGORY_HOME)
+                .addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK
+                        | FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS | FLAG_ACTIVITY_NO_ANIMATION);
+        ResolveInfo ri = context.getPackageManager().resolveActivity(
+                new Intent(homeIntent).setPackage(context.getPackageName()), 0);
+        homeIntent.setComponent(ri.getComponentInfo().getComponentName());
+        return new LazyActivityRule<>(() -> ActivityScenario.launch(homeIntent));
+    }
+}
diff --git a/tests/src/com/android/launcher3/util/rule/SimpleActivityRule.java b/tests/src/com/android/launcher3/util/rule/SimpleActivityRule.java
index 2eedec3..b5d8193 100644
--- a/tests/src/com/android/launcher3/util/rule/SimpleActivityRule.java
+++ b/tests/src/com/android/launcher3/util/rule/SimpleActivityRule.java
@@ -22,6 +22,8 @@
 
 import androidx.test.InstrumentationRegistry;
 
+import com.android.launcher3.testing.shared.TestProtocol;
+
 import org.junit.rules.TestRule;
 import org.junit.runner.Description;
 import org.junit.runners.model.Statement;
@@ -71,33 +73,57 @@
         @Override
         public void onActivityCreated(Activity activity, Bundle bundle) {
             if (activity != null && mClass.isInstance(activity)) {
+                TestProtocol.testLogD(
+                        TestProtocol.ACTIVITY_LIFECYCLE_RULE, "MyStatement.onActivityCreated");
                 mActivity = (T) activity;
             }
         }
 
         @Override
         public void onActivityStarted(Activity activity) {
+            if (activity == mActivity) {
+                TestProtocol.testLogD(
+                        TestProtocol.ACTIVITY_LIFECYCLE_RULE, "MyStatement.onActivityStarted");
+            }
         }
 
         @Override
         public void onActivityResumed(Activity activity) {
+            if (activity == mActivity) {
+                TestProtocol.testLogD(
+                        TestProtocol.ACTIVITY_LIFECYCLE_RULE, "MyStatement.onActivityResumed");
+            }
         }
 
         @Override
         public void onActivityPaused(Activity activity) {
+            if (activity == mActivity) {
+                TestProtocol.testLogD(
+                        TestProtocol.ACTIVITY_LIFECYCLE_RULE, "MyStatement.onActivityPaused");
+            }
         }
 
         @Override
         public void onActivityStopped(Activity activity) {
+            if (activity == mActivity) {
+                TestProtocol.testLogD(
+                        TestProtocol.ACTIVITY_LIFECYCLE_RULE, "MyStatement.onAcgtivityStopped");
+            }
         }
 
         @Override
         public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
+            if (activity == mActivity) {
+                TestProtocol.testLogD(TestProtocol.ACTIVITY_LIFECYCLE_RULE,
+                        "MyStatement.onActivitySaveInstanceState");
+            }
         }
 
         @Override
         public void onActivityDestroyed(Activity activity) {
             if (activity == mActivity) {
+                TestProtocol.testLogD(
+                        TestProtocol.ACTIVITY_LIFECYCLE_RULE, "MyStatement.onActivityDestroyed");
                 mActivity = null;
             }
         }
diff --git a/tests/src/com/android/launcher3/util/rule/ViewCaptureAnalysisRule.java b/tests/src/com/android/launcher3/util/rule/ViewCaptureAnalysisRule.java
deleted file mode 100644
index 702757f..0000000
--- a/tests/src/com/android/launcher3/util/rule/ViewCaptureAnalysisRule.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.util.rule;
-
-import androidx.annotation.NonNull;
-import androidx.test.InstrumentationRegistry;
-
-import com.android.app.viewcapture.ViewCapture;
-import com.android.app.viewcapture.data.ExportedData;
-
-import org.junit.rules.TestWatcher;
-import org.junit.runner.Description;
-
-import java.util.concurrent.ExecutionException;
-
-/**
- * After the test succeeds, the rule looks for anomalies in the data accumulated by ViewCapture
- * that's passed as a parameter. If anomalies are detected, throws an exception and fails the test.
- */
-public class ViewCaptureAnalysisRule extends TestWatcher {
-    @NonNull
-    private final ViewCapture mViewCapture;
-
-    public ViewCaptureAnalysisRule(@NonNull ViewCapture viewCapture) {
-        mViewCapture = viewCapture;
-    }
-
-    @Override
-    protected void succeeded(Description description) {
-        super.succeeded(description);
-        try {
-            analyzeViewCaptureData(mViewCapture.getExportedData(
-                    InstrumentationRegistry.getTargetContext()));
-        } catch (InterruptedException e) {
-            throw new RuntimeException(e);
-        } catch (ExecutionException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    private static void analyzeViewCaptureData(ExportedData viewCaptureData) {
-    }
-}
diff --git a/tests/src/com/android/launcher3/util/rule/ViewCaptureRule.kt b/tests/src/com/android/launcher3/util/rule/ViewCaptureRule.kt
index 6c06502..8e2aea8 100644
--- a/tests/src/com/android/launcher3/util/rule/ViewCaptureRule.kt
+++ b/tests/src/com/android/launcher3/util/rule/ViewCaptureRule.kt
@@ -36,7 +36,7 @@
  * This rule will not work in OOP tests that don't have access to the activity under test.
  */
 class ViewCaptureRule(var alreadyOpenActivitySupplier: Supplier<Activity?>) : TestRule {
-    val viewCapture = SimpleViewCapture("test-view-capture")
+    private val viewCapture = SimpleViewCapture("test-view-capture")
     var viewCaptureData: ExportedData? = null
         private set
 
diff --git a/tests/src/com/android/launcher3/util/rule/WrapperRule.kt b/tests/src/com/android/launcher3/util/rule/WrapperRule.kt
new file mode 100644
index 0000000..290cc45
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/rule/WrapperRule.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.android.launcher3.util.rule
+
+import com.android.launcher3.config.FeatureFlags.BooleanFlag
+import com.android.launcher3.config.FeatureFlags.IntFlag
+import com.android.launcher3.util.SafeCloseable
+import com.android.launcher3.util.TestUtil
+import java.util.function.Supplier
+import org.junit.rules.ExternalResource
+
+/** Simple rule which wraps any SafeCloseable object */
+class WrapperRule(private val overrideProvider: Supplier<SafeCloseable>) : ExternalResource() {
+
+    private lateinit var overrideClosable: SafeCloseable
+
+    override fun before() {
+        overrideClosable = overrideProvider.get()
+    }
+
+    override fun after() {
+        overrideClosable.close()
+    }
+
+    companion object {
+
+        fun BooleanFlag.overrideFlag(value: Boolean) = WrapperRule {
+            TestUtil.overrideFlag(this, value)
+        }
+
+        fun IntFlag.overrideFlag(value: Int) = WrapperRule { TestUtil.overrideFlag(this, value) }
+    }
+}
diff --git a/tests/src/com/android/launcher3/util/viewcapture_analysis/AlphaJumpDetector.java b/tests/src/com/android/launcher3/util/viewcapture_analysis/AlphaJumpDetector.java
new file mode 100644
index 0000000..e40fb79
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/viewcapture_analysis/AlphaJumpDetector.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.util.viewcapture_analysis;
+
+import static com.android.launcher3.util.viewcapture_analysis.ViewCaptureAnalyzer.diagPathFromRoot;
+
+import com.android.launcher3.util.viewcapture_analysis.ViewCaptureAnalyzer.AnalysisNode;
+import com.android.launcher3.util.viewcapture_analysis.ViewCaptureAnalyzer.AnomalyDetector;
+
+import java.util.Collection;
+import java.util.Set;
+
+/**
+ * Anomaly detector that triggers an error when alpha of a view changes too rapidly.
+ * Invisible views are treated as if they had zero alpha.
+ */
+final class AlphaJumpDetector extends AnomalyDetector {
+    // Paths of nodes that are excluded from analysis.
+    private static final Collection<String> PATHS_TO_IGNORE = Set.of(
+            "DecorView|LinearLayout|FrameLayout:id/content|LauncherRootView:id/launcher|DragLayer"
+                    + ":id/drag_layer|SearchContainerView:id/apps_view|SearchRecyclerView:id"
+                    + "/search_results_list_view|SearchResultSmallIconRow",
+            "DecorView|LinearLayout|FrameLayout:id/content|LauncherRootView:id/launcher|DragLayer"
+                    + ":id/drag_layer|SearchContainerView:id/apps_view|SearchRecyclerView:id"
+                    + "/search_results_list_view|SearchResultIcon",
+            "DecorView|LinearLayout|FrameLayout:id/content|LauncherRootView:id/launcher|DragLayer"
+                    + ":id/drag_layer|LauncherRecentsView:id/overview_panel|TaskView",
+            "DecorView|LinearLayout|FrameLayout:id/content|LauncherRootView:id/launcher|DragLayer"
+                    + ":id/drag_layer|WidgetsFullSheet|SpringRelativeLayout:id/container"
+                    + "|WidgetsRecyclerView:id/primary_widgets_list_view|WidgetsListHeader:id"
+                    + "/widgets_list_header",
+            "DecorView|LinearLayout|FrameLayout:id/content|LauncherRootView:id/launcher|DragLayer"
+                    + ":id/drag_layer|WidgetsFullSheet|SpringRelativeLayout:id/container"
+                    + "|WidgetsRecyclerView:id/primary_widgets_list_view"
+                    + "|StickyHeaderLayout$EmptySpaceView",
+            "DecorView|LinearLayout|FrameLayout:id/content|LauncherRootView:id/launcher|DragLayer"
+                    + ":id/drag_layer|SearchContainerView:id/apps_view|AllAppsRecyclerView:id"
+                    + "/apps_list_view|BubbleTextView:id/icon",
+            "DecorView|LinearLayout|FrameLayout:id/content|LauncherRootView:id/launcher|DragLayer"
+                    + ":id/drag_layer|LauncherRecentsView:id/overview_panel|ClearAllButton:id"
+                    + "/clear_all",
+            "DecorView|LinearLayout|FrameLayout:id/content|LauncherRootView:id/launcher|DragLayer"
+                    + ":id/drag_layer|NexusOverviewActionsView:id/overview_actions_view"
+                    + "|LinearLayout:id/action_buttons"
+    );
+    // Minimal increase or decrease of view's alpha between frames that triggers the error.
+    private static final float ALPHA_JUMP_THRESHOLD = 1f;
+
+    @Override
+    void initializeNode(AnalysisNode info) {
+        // If the parent view ignores alpha jumps, its descendants will too.
+        final boolean parentIgnoreAlphaJumps = info.parent != null && info.parent.ignoreAlphaJumps;
+        info.ignoreAlphaJumps = parentIgnoreAlphaJumps
+                || PATHS_TO_IGNORE.contains(diagPathFromRoot(info));
+    }
+
+    @Override
+    void detectAnomalies(AnalysisNode oldInfo, AnalysisNode newInfo, int frameN) {
+        // If the view was previously seen, proceed with analysis only if it was present in the
+        // view hierarchy in the previous frame.
+        if (oldInfo != null && oldInfo.frameN != frameN) return;
+
+        final AnalysisNode latestInfo = newInfo != null ? newInfo : oldInfo;
+        if (latestInfo.ignoreAlphaJumps) return;
+
+        final float oldAlpha = oldInfo != null ? oldInfo.alpha : 0;
+        final float newAlpha = newInfo != null ? newInfo.alpha : 0;
+        final float alphaDeltaAbs = Math.abs(newAlpha - oldAlpha);
+
+        if (alphaDeltaAbs >= ALPHA_JUMP_THRESHOLD) {
+            throw new AssertionError(
+                    String.format(
+                            "Alpha jump detected in ViewCapture data: alpha change: %s (%s -> %s)"
+                                    + ", threshold: %s, view: %s",
+                            alphaDeltaAbs, oldAlpha, newAlpha, ALPHA_JUMP_THRESHOLD, latestInfo));
+        }
+    }
+}
diff --git a/tests/src/com/android/launcher3/util/viewcapture_analysis/ViewCaptureAnalyzer.java b/tests/src/com/android/launcher3/util/viewcapture_analysis/ViewCaptureAnalyzer.java
new file mode 100644
index 0000000..5a2611c
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/viewcapture_analysis/ViewCaptureAnalyzer.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.util.viewcapture_analysis;
+
+import static android.view.View.VISIBLE;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.app.viewcapture.data.ExportedData;
+import com.android.app.viewcapture.data.FrameData;
+import com.android.app.viewcapture.data.ViewNode;
+import com.android.app.viewcapture.data.WindowData;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Utility that analyzes ViewCapture data and finds anomalies such as views appearing or
+ * disappearing without alpha-fading.
+ */
+public class ViewCaptureAnalyzer {
+    private static final String SCRIM_VIEW_CLASS = "com.android.launcher3.views.ScrimView";
+
+    /**
+     * Detector of one kind of anomaly.
+     */
+    abstract static class AnomalyDetector {
+        /**
+         * Initializes fields of the node that are specific to the anomaly detected by this
+         * detector.
+         */
+        abstract void initializeNode(@NonNull AnalysisNode info);
+
+        /**
+         * Detects anomalies by looking at the last occurrence of a view, and the current one.
+         * null value means that the view. 'oldInfo' and 'newInfo' cannot be both null.
+         * If an anomaly is detected, an exception will be thrown.
+         *
+         * @param oldInfo the view, as seen in the last frame that contained it in the view
+         *                hierarchy before 'currentFrame'. 'null' means that the view is first seen
+         *                in the 'currentFrame'.
+         * @param newInfo the view in the view hierarchy of the 'currentFrame'. 'null' means that
+         *                the view is not present in the 'currentFrame', but was present in earlier
+         *                frames.
+         * @param frameN  number of the current frame.
+         */
+        abstract void detectAnomalies(
+                @Nullable AnalysisNode oldInfo, @Nullable AnalysisNode newInfo, int frameN);
+    }
+
+    // All detectors. They will be invoked in the order listed here.
+    private static final Iterable<AnomalyDetector> ANOMALY_DETECTORS = Arrays.asList(
+            new AlphaJumpDetector()
+    );
+
+    // A view from view capture data converted to a form that's convenient for detecting anomalies.
+    static class AnalysisNode {
+        public String className;
+        public String resourceId;
+        public AnalysisNode parent;
+
+        // Window coordinates of the view.
+        public float left;
+        public float top;
+
+        // Visible scale and alpha, build recursively from the ancestor list.
+        public float scaleX;
+        public float scaleY;
+        public float alpha;
+
+        public int frameN;
+        public ViewNode viewCaptureNode;
+
+        public boolean ignoreAlphaJumps;
+
+        @Override
+        public String toString() {
+            return String.format("window coordinates: (%s, %s), class path from the root: %s",
+                    left, top, diagPathFromRoot(this));
+        }
+    }
+
+    /**
+     * Scans a view capture record and throws an error if an anomaly is found.
+     */
+    public static void assertNoAnomalies(ExportedData viewCaptureData) {
+        final int scrimClassIndex = viewCaptureData.getClassnameList().indexOf(SCRIM_VIEW_CLASS);
+
+        final int windowDataCount = viewCaptureData.getWindowDataCount();
+        for (int i = 0; i < windowDataCount; ++i) {
+            analyzeWindowData(viewCaptureData, viewCaptureData.getWindowData(i), scrimClassIndex);
+        }
+    }
+
+    private static void analyzeWindowData(ExportedData viewCaptureData, WindowData windowData,
+            int scrimClassIndex) {
+        // View hash code => Last seen node with this hash code.
+        // The view is added when we analyze the first frame where it's visible.
+        // After that, it gets updated for every frame where it's visible.
+        // As we go though frames, if a view becomes invisible, it stays in the map.
+        final Map<Integer, AnalysisNode> lastSeenNodes = new HashMap<>();
+
+        for (int frameN = 0; frameN < windowData.getFrameDataCount(); ++frameN) {
+            analyzeFrame(frameN, windowData.getFrameData(frameN), viewCaptureData, lastSeenNodes,
+                    scrimClassIndex);
+        }
+    }
+
+    private static void analyzeFrame(int frameN, FrameData frame, ExportedData viewCaptureData,
+            Map<Integer, AnalysisNode> lastSeenNodes, int scrimClassIndex) {
+        // Analyze the node tree starting from the root.
+        analyzeView(
+                frame.getNode(),
+                /* parent = */ null,
+                frameN,
+                /* leftShift = */ 0,
+                /* topShift = */ 0,
+                viewCaptureData,
+                lastSeenNodes,
+                scrimClassIndex);
+
+        // Analyze transitions when a view visible in the last frame become invisible in the
+        // current one.
+        for (AnalysisNode info : lastSeenNodes.values()) {
+            if (info.frameN == frameN - 1) {
+                if (!info.viewCaptureNode.getWillNotDraw()) {
+                    ANOMALY_DETECTORS.forEach(
+                            detector -> detector.detectAnomalies(
+                                    /* oldInfo = */ info,
+                                    /* newInfo = */ null,
+                                    frameN));
+                }
+            }
+        }
+    }
+
+    private static void analyzeView(ViewNode viewCaptureNode, AnalysisNode parent, int frameN,
+            float leftShift, float topShift, ExportedData viewCaptureData,
+            Map<Integer, AnalysisNode> lastSeenNodes, int scrimClassIndex) {
+        // Skip analysis of invisible views
+        final float parentAlpha = parent != null ? parent.alpha : 1;
+        final float alpha = getVisibleAlpha(viewCaptureNode, parentAlpha);
+        if (alpha <= 0.0) return;
+
+        // Calculate analysis node parameters
+        final int hashcode = viewCaptureNode.getHashcode();
+        final int classIndex = viewCaptureNode.getClassnameIndex();
+
+        final float parentScaleX = parent != null ? parent.scaleX : 1;
+        final float parentScaleY = parent != null ? parent.scaleY : 1;
+        final float scaleX = parentScaleX * viewCaptureNode.getScaleX();
+        final float scaleY = parentScaleY * viewCaptureNode.getScaleY();
+
+        final float left = leftShift
+                + (viewCaptureNode.getLeft() + viewCaptureNode.getTranslationX()) * parentScaleX
+                + viewCaptureNode.getWidth() * (parentScaleX - scaleX) / 2;
+        final float top = topShift
+                + (viewCaptureNode.getTop() + viewCaptureNode.getTranslationY()) * parentScaleY
+                + viewCaptureNode.getHeight() * (parentScaleY - scaleY) / 2;
+
+        // Initialize new analysis node
+        final AnalysisNode newAnalysisNode = new AnalysisNode();
+        newAnalysisNode.className = viewCaptureData.getClassname(classIndex);
+        newAnalysisNode.resourceId = viewCaptureNode.getId();
+        newAnalysisNode.parent = parent;
+        newAnalysisNode.left = left;
+        newAnalysisNode.top = top;
+        newAnalysisNode.scaleX = scaleX;
+        newAnalysisNode.scaleY = scaleY;
+        newAnalysisNode.alpha = alpha;
+        newAnalysisNode.frameN = frameN;
+        newAnalysisNode.viewCaptureNode = viewCaptureNode;
+        ANOMALY_DETECTORS.forEach(detector -> detector.initializeNode(newAnalysisNode));
+
+        // Detect anomalies for the view
+        final AnalysisNode oldAnalysisNode = lastSeenNodes.get(hashcode); // may be null
+        if (frameN != 0 && !viewCaptureNode.getWillNotDraw()) {
+            ANOMALY_DETECTORS.forEach(
+                    detector -> detector.detectAnomalies(oldAnalysisNode, newAnalysisNode, frameN));
+        }
+        lastSeenNodes.put(hashcode, newAnalysisNode);
+
+        // Enumerate children starting from the topmost one. Stop at ScrimView, if present.
+        final float leftShiftForChildren = left - viewCaptureNode.getScrollX();
+        final float topShiftForChildren = top - viewCaptureNode.getScrollY();
+        for (int i = viewCaptureNode.getChildrenCount() - 1; i >= 0; --i) {
+            final ViewNode child = viewCaptureNode.getChildren(i);
+
+            // Don't analyze anything under scrim view because we don't know whether it's
+            // transparent.
+            if (child.getClassnameIndex() == scrimClassIndex) break;
+
+            analyzeView(child, newAnalysisNode, frameN, leftShiftForChildren, topShiftForChildren,
+                    viewCaptureData, lastSeenNodes,
+                    scrimClassIndex);
+        }
+    }
+
+    private static float getVisibleAlpha(ViewNode node, float parenVisibleAlpha) {
+        return node.getVisibility() == VISIBLE
+                ? parenVisibleAlpha * Math.max(0, Math.min(node.getAlpha(), 1))
+                : 0f;
+    }
+
+    private static String classNameToSimpleName(String className) {
+        return className.substring(className.lastIndexOf(".") + 1);
+    }
+
+    static String diagPathFromRoot(AnalysisNode nodeBox) {
+        final StringBuilder path = new StringBuilder(diagPathElement(nodeBox));
+        for (AnalysisNode ancestor = nodeBox.parent; ancestor != null; ancestor = ancestor.parent) {
+            path.insert(0, diagPathElement(ancestor) + "|");
+        }
+        return path.toString();
+    }
+
+    private static String diagPathElement(AnalysisNode nodeBox) {
+        final StringBuilder sb = new StringBuilder();
+        sb.append(classNameToSimpleName(nodeBox.className));
+        if (!"NO_ID".equals(nodeBox.resourceId)) sb.append(":" + nodeBox.resourceId);
+        return sb.toString();
+    }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/Background.java b/tests/tapl/com/android/launcher3/tapl/Background.java
index 31e9aa2..3dab9a8 100644
--- a/tests/tapl/com/android/launcher3/tapl/Background.java
+++ b/tests/tapl/com/android/launcher3/tapl/Background.java
@@ -30,6 +30,8 @@
 import androidx.annotation.NonNull;
 import androidx.test.uiautomator.UiObject2;
 
+import com.android.launcher3.tapl.LauncherInstrumentation.NavigationModel;
+import com.android.launcher3.tapl.LauncherInstrumentation.TrackpadGestureType;
 import com.android.launcher3.testing.shared.TestProtocol;
 
 import java.util.List;
@@ -75,80 +77,76 @@
     }
 
     protected void goToOverviewUnchecked() {
-        switch (mLauncher.getNavigationModel()) {
-            case ZERO_BUTTON: {
-                final long downTime = SystemClock.uptimeMillis();
-                sendDownPointerToEnterOverviewToLauncher(downTime);
-                String swipeAndHoldToEnterOverviewActionName =
-                        "swiping and holding to enter overview";
-                // If swiping from an app (e.g. Overview is in Background), we pause and hold on
-                // swipe up to make overview appear, or else swiping without holding would take
-                // us to the Home state. If swiping up from Home (e.g. Overview in Home or
-                // Workspace state where the below condition is true), there is no need to pause,
-                // and we will not test for an intermediate carousel as one will not exist.
-                if (zeroButtonToOverviewGestureStateTransitionWhileHolding()) {
-                    mLauncher.runToState(
-                            () -> sendSwipeUpAndHoldToEnterOverviewGestureToLauncher(downTime),
-                            OVERVIEW_STATE_ORDINAL, swipeAndHoldToEnterOverviewActionName);
-                    sendUpPointerToEnterOverviewToLauncher(downTime);
-                } else {
-                    // If swiping up from an app to overview, pause on intermediate carousel
-                    // until snapshots are visible. No intermediate carousel when swiping from
-                    // Home. The task swiped up is not a snapshot but the TaskViewSimulator. If
-                    // only a single task exists, no snapshots will be available during swipe up.
-                    mLauncher.executeAndWaitForLauncherEvent(
-                            () -> sendSwipeUpAndHoldToEnterOverviewGestureToLauncher(downTime),
-                            event -> TestProtocol.PAUSE_DETECTED_MESSAGE.equals(
-                                    event.getClassName().toString()),
-                            () -> "Pause wasn't detected",
-                            swipeAndHoldToEnterOverviewActionName);
-                    try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
-                            "paused on swipe up to overview")) {
-                        if (mLauncher.getRecentTasks().size() > 1) {
-                            // When swiping up to grid-overview for tablets, the swiped tab will be
-                            // in the middle of the screen (TaskViewSimulator, not a snapshot), and
-                            // all remaining snapshots will be to the left of that task. In
-                            // non-tablet overview, snapshots can be on either side of the swiped
-                            // task, but we still check that they become visible after swiping and
-                            // pausing.
-                            mLauncher.waitForOverviewObject("snapshot");
-                            if (mLauncher.isTablet()) {
-                                List<UiObject2> tasks = mLauncher.getDevice().findObjects(
-                                        mLauncher.getOverviewObjectSelector("snapshot"));
-                                final int centerX = mLauncher.getDevice().getDisplayWidth() / 2;
-                                mLauncher.assertTrue(
-                                        "All tasks not to the left of the swiped task",
-                                        tasks.stream()
-                                                .allMatch(
-                                                        t -> t.getVisibleBounds().right < centerX));
-                            }
-
-                        }
-                        String upPointerToEnterOverviewActionName =
-                                "sending UP pointer to enter overview";
-                        mLauncher.runToState(() -> sendUpPointerToEnterOverviewToLauncher(downTime),
-                                OVERVIEW_STATE_ORDINAL, upPointerToEnterOverviewActionName);
-                    }
-                }
-                break;
-            }
-
-            case THREE_BUTTON:
-                if (mLauncher.isTablet()) {
-                    mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN,
-                            LauncherInstrumentation.EVENT_TOUCH_DOWN);
-                    mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN,
-                            LauncherInstrumentation.EVENT_TOUCH_UP);
-                }
-                if (mLauncher.isTrackpadGestureEnabled()) {
-                    mLauncher.expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_TOUCH_DOWN_TIS);
-                    mLauncher.expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_TOUCH_UP_TIS);
-                }
-                mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, SQUARE_BUTTON_EVENT);
+        if (mLauncher.getNavigationModel() == NavigationModel.ZERO_BUTTON
+                || mLauncher.getTrackpadGestureType() == TrackpadGestureType.THREE_FINGER) {
+            final long downTime = SystemClock.uptimeMillis();
+            sendDownPointerToEnterOverviewToLauncher(downTime);
+            String swipeAndHoldToEnterOverviewActionName =
+                    "swiping and holding to enter overview";
+            // If swiping from an app (e.g. Overview is in Background), we pause and hold on
+            // swipe up to make overview appear, or else swiping without holding would take
+            // us to the Home state. If swiping up from Home (e.g. Overview in Home or
+            // Workspace state where the below condition is true), there is no need to pause,
+            // and we will not test for an intermediate carousel as one will not exist.
+            if (zeroButtonToOverviewGestureStateTransitionWhileHolding()) {
                 mLauncher.runToState(
-                        () -> mLauncher.waitForNavigationUiObject("recent_apps").click(),
-                        OVERVIEW_STATE_ORDINAL, "clicking Recents button");
-                break;
+                        () -> sendSwipeUpAndHoldToEnterOverviewGestureToLauncher(downTime),
+                        OVERVIEW_STATE_ORDINAL, swipeAndHoldToEnterOverviewActionName);
+                sendUpPointerToEnterOverviewToLauncher(downTime);
+            } else {
+                // If swiping up from an app to overview, pause on intermediate carousel
+                // until snapshots are visible. No intermediate carousel when swiping from
+                // Home. The task swiped up is not a snapshot but the TaskViewSimulator. If
+                // only a single task exists, no snapshots will be available during swipe up.
+                mLauncher.executeAndWaitForLauncherEvent(
+                        () -> sendSwipeUpAndHoldToEnterOverviewGestureToLauncher(downTime),
+                        event -> TestProtocol.PAUSE_DETECTED_MESSAGE.equals(
+                                event.getClassName().toString()),
+                        () -> "Pause wasn't detected",
+                        swipeAndHoldToEnterOverviewActionName);
+                try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                        "paused on swipe up to overview")) {
+                    if (mLauncher.getRecentTasks().size() > 1) {
+                        // When swiping up to grid-overview for tablets, the swiped tab will be
+                        // in the middle of the screen (TaskViewSimulator, not a snapshot), and
+                        // all remaining snapshots will be to the left of that task. In
+                        // non-tablet overview, snapshots can be on either side of the swiped
+                        // task, but we still check that they become visible after swiping and
+                        // pausing.
+                        mLauncher.waitForOverviewObject("snapshot");
+                        if (mLauncher.isTablet()) {
+                            List<UiObject2> tasks = mLauncher.getDevice().findObjects(
+                                    mLauncher.getOverviewObjectSelector("snapshot"));
+                            final int centerX = mLauncher.getDevice().getDisplayWidth() / 2;
+                            mLauncher.assertTrue(
+                                    "All tasks not to the left of the swiped task",
+                                    tasks.stream()
+                                            .allMatch(
+                                                    t -> t.getVisibleBounds().right < centerX));
+                        }
+
+                    }
+                    String upPointerToEnterOverviewActionName =
+                            "sending UP pointer to enter overview";
+                    mLauncher.runToState(() -> sendUpPointerToEnterOverviewToLauncher(downTime),
+                            OVERVIEW_STATE_ORDINAL, upPointerToEnterOverviewActionName);
+                }
+            }
+        } else {
+            if (mLauncher.isTablet()) {
+                mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN,
+                        LauncherInstrumentation.EVENT_TOUCH_DOWN);
+                mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN,
+                        LauncherInstrumentation.EVENT_TOUCH_UP);
+            }
+            if (mLauncher.isTrackpadGestureEnabled()) {
+                mLauncher.expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_TOUCH_DOWN_TIS);
+                mLauncher.expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_TOUCH_UP_TIS);
+            }
+            mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, SQUARE_BUTTON_EVENT);
+            mLauncher.runToState(
+                    () -> mLauncher.waitForNavigationUiObject("recent_apps").click(),
+                    OVERVIEW_STATE_ORDINAL, "clicking Recents button");
         }
         expectSwitchToOverviewEvents();
     }
@@ -263,10 +261,8 @@
                         endY = startY;
                     }
 
-                    final boolean isZeroButton = mLauncher.getNavigationModel()
-                            == LauncherInstrumentation.NavigationModel.ZERO_BUTTON;
                     LauncherInstrumentation.GestureScope gestureScope =
-                            launcherWasVisible && isZeroButton
+                            launcherWasVisible
                                     ? LauncherInstrumentation.GestureScope.INSIDE_TO_OUTSIDE
                                     : LauncherInstrumentation.GestureScope.OUTSIDE_WITH_PILFER;
                     mLauncher.executeAndWaitForEvent(
@@ -326,6 +322,8 @@
     }
 
     protected int getSwipeStartY() {
-        return mLauncher.getRealDisplaySize().y - 1;
+        return mLauncher.getTrackpadGestureType() == TrackpadGestureType.THREE_FINGER
+                ? mLauncher.getDevice().getDisplayHeight() * 3 / 4
+                : mLauncher.getRealDisplaySize().y - 1;
     }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 2286d7e..89f141f 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -20,6 +20,7 @@
 import static android.content.pm.PackageManager.DONT_KILL_APP;
 import static android.content.pm.PackageManager.MATCH_ALL;
 import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS;
+import static android.view.MotionEvent.AXIS_GESTURE_SWIPE_FINGER_COUNT;
 
 import static com.android.launcher3.tapl.Folder.FOLDER_CONTENT_RES_ID;
 import static com.android.launcher3.tapl.TestHelpers.getOverviewPackageName;
@@ -138,6 +139,13 @@
         OUTSIDE_WITH_KEYCODE,
     }
 
+    public enum TrackpadGestureType {
+        NONE,
+        TWO_FINGER,
+        THREE_FINGER,
+        FOUR_FINGER
+    }
+
     // Base class for launcher containers.
     abstract static class VisibleContainer {
         protected final LauncherInstrumentation mLauncher;
@@ -197,21 +205,36 @@
     private boolean mCheckEventsForSuccessfulGestures = false;
     private Runnable mOnLauncherCrashed;
 
+    private TrackpadGestureType mTrackpadGestureType = TrackpadGestureType.NONE;
+    private int mPointerCount = 0;
+
     private static Pattern getTouchEventPattern(String prefix, String action) {
-        // The pattern includes checks that we don't get a multi-touch events or other surprises.
+        return getTouchEventPattern(prefix, action, 1);
+    }
+
+    private static Pattern getTouchEventPattern(String prefix, String action, int pointerCount) {
         return Pattern.compile(
                 prefix + ": MotionEvent.*?action=" + action + ".*?id\\[0\\]=0"
-                        + ".*?toolType\\[0\\]=TOOL_TYPE_FINGER.*?buttonState=0.*?pointerCount=1");
+                        + ".*?toolType\\[0\\]=TOOL_TYPE_FINGER.*?buttonState=0.*?pointerCount="
+                        + pointerCount);
     }
 
     private static Pattern getTouchEventPattern(String action) {
         return getTouchEventPattern("Touch event", action);
     }
 
+    private static Pattern getTouchEventPattern(String action, int pointerCount) {
+        return getTouchEventPattern("Touch event", action, pointerCount);
+    }
+
     private static Pattern getTouchEventPatternTIS(String action) {
         return getTouchEventPattern("TouchInteractionService.onInputEvent", action);
     }
 
+    private static Pattern getTouchEventPatternTIS(String action, int pointerCount) {
+        return getTouchEventPattern("TouchInteractionService.onInputEvent", action, pointerCount);
+    }
+
     private static Pattern getKeyEventPattern(String action, String keyCode) {
         return Pattern.compile("Key event: KeyEvent.*action=" + action + ".*keyCode=" + keyCode);
     }
@@ -704,6 +727,19 @@
     }
 
     /**
+     * Set the trackpad gesture type of the interaction.
+     * @param trackpadGestureType whether it's not from trackpad, two-finger, three-finger, or
+     *                            four-finger gesture.
+     */
+    public void setTrackpadGestureType(TrackpadGestureType trackpadGestureType) {
+        mTrackpadGestureType = trackpadGestureType;
+    }
+
+    TrackpadGestureType getTrackpadGestureType() {
+        return mTrackpadGestureType;
+    }
+
+    /**
      * Sets expected rotation.
      * TAPL periodically checks that Launcher didn't suddenly change the rotation to unexpected one.
      * Null parameter disables checks. The initial state is "no checks".
@@ -992,8 +1028,11 @@
             // We need waiting for any accessibility event generated after pressing Home because
             // otherwise waitForIdle may return immediately in case when there was a big enough
             // pause in accessibility events prior to pressing Home.
+            boolean isThreeFingerTrackpadGesture =
+                    mTrackpadGestureType == TrackpadGestureType.THREE_FINGER;
             final String action;
-            if (getNavigationModel() == NavigationModel.ZERO_BUTTON) {
+            if (getNavigationModel() == NavigationModel.ZERO_BUTTON
+                    || isThreeFingerTrackpadGesture) {
                 checkForAnomaly(false, true);
 
                 final Point displaySize = getRealDisplaySize();
@@ -1009,9 +1048,12 @@
                 } else {
                     action = "swiping up to home";
 
+                    int startY = isThreeFingerTrackpadGesture ? displaySize.y * 3 / 4
+                            : displaySize.y - 1;
+                    int endY = isThreeFingerTrackpadGesture ? displaySize.y / 4 : displaySize.y / 2;
                     swipeToState(
-                            displaySize.x / 2, displaySize.y - 1,
-                            displaySize.x / 2, displaySize.y / 2,
+                            displaySize.x / 2, startY,
+                            displaySize.x / 2, endY,
                             ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME, NORMAL_STATE_ORDINAL,
                             gestureStartFromLauncher ? GestureScope.INSIDE_TO_OUTSIDE
                                     : GestureScope.OUTSIDE_WITH_PILFER);
@@ -1052,14 +1094,19 @@
             waitForLauncherInitialized();
             final boolean launcherVisible =
                     isTablet() ? isLauncherContainerVisible() : isLauncherVisible();
-            if (getNavigationModel() == NavigationModel.ZERO_BUTTON) {
+            boolean isThreeFingerTrackpadGesture =
+                    mTrackpadGestureType == TrackpadGestureType.THREE_FINGER;
+            if (getNavigationModel() == NavigationModel.ZERO_BUTTON
+                    || isThreeFingerTrackpadGesture) {
                 final Point displaySize = getRealDisplaySize();
                 final GestureScope gestureScope =
                         launcherVisible ? GestureScope.INSIDE_TO_OUTSIDE_WITH_KEYCODE
                                 : GestureScope.OUTSIDE_WITH_KEYCODE;
                 // TODO(b/225505986): change startY and endY back to displaySize.y / 2 once the
                 //  issue is solved.
-                linearGesture(0, displaySize.y / 4, displaySize.x / 2, displaySize.y / 4,
+                int startX = isThreeFingerTrackpadGesture ? displaySize.x / 4 : 0;
+                int endX = isThreeFingerTrackpadGesture ? displaySize.x * 3 / 4 : displaySize.x / 2;
+                linearGesture(startX, displaySize.y / 4, endX, displaySize.y / 4,
                         10, false, gestureScope);
             } else {
                 waitForNavigationUiObject("back").click();
@@ -1589,18 +1636,43 @@
     // Inject a swipe gesture. Inject exactly 'steps' motion points, incrementing event time by a
     // fixed interval each time.
     public void linearGesture(int startX, int startY, int endX, int endY, int steps,
-            boolean slowDown,
-            GestureScope gestureScope) {
+            boolean slowDown, GestureScope gestureScope) {
         log("linearGesture: " + startX + ", " + startY + " -> " + endX + ", " + endY);
         final long downTime = SystemClock.uptimeMillis();
         final Point start = new Point(startX, startY);
         final Point end = new Point(endX, endY);
         sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, start, gestureScope);
+        if (mTrackpadGestureType != TrackpadGestureType.NONE) {
+            sendPointer(downTime, downTime, getPointerAction(MotionEvent.ACTION_POINTER_DOWN, 1),
+                    start, gestureScope);
+            if (mTrackpadGestureType == TrackpadGestureType.THREE_FINGER
+                    || mTrackpadGestureType == TrackpadGestureType.FOUR_FINGER) {
+                sendPointer(downTime, downTime,
+                        getPointerAction(MotionEvent.ACTION_POINTER_DOWN, 2),
+                        start, gestureScope);
+                if (mTrackpadGestureType == TrackpadGestureType.FOUR_FINGER) {
+                    sendPointer(downTime, downTime,
+                            getPointerAction(MotionEvent.ACTION_POINTER_DOWN, 3),
+                            start, gestureScope);
+                }
+            }
+        }
         final long endTime = movePointer(
                 start, end, steps, false, downTime, downTime, slowDown, gestureScope);
+        if (mTrackpadGestureType != TrackpadGestureType.NONE) {
+            for (int i = mPointerCount; i >= 2; i--) {
+                sendPointer(downTime, downTime,
+                        getPointerAction(MotionEvent.ACTION_POINTER_UP, i - 1),
+                        start, gestureScope);
+            }
+        }
         sendPointer(downTime, endTime, MotionEvent.ACTION_UP, end, gestureScope);
     }
 
+    private static int getPointerAction(int action, int index) {
+        return action + (index << MotionEvent.ACTION_POINTER_INDEX_SHIFT);
+    }
+
     long movePointer(Point start, Point end, int steps, boolean isDecelerating, long downTime,
             long startTime, boolean slowDown, GestureScope gestureScope) {
         long endTime = movePointer(downTime, startTime, steps * GESTURE_STEP_MS,
@@ -1624,22 +1696,49 @@
         return getContext().getResources();
     }
 
+    private static MotionEvent getTrackpadMotionEvent(long downTime, long eventTime,
+            int action, float x, float y, int pointerCount, TrackpadGestureType gestureType) {
+        MotionEvent.PointerProperties[] pointerProperties =
+                new MotionEvent.PointerProperties[pointerCount];
+        MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[pointerCount];
+        boolean isMultiFingerGesture = gestureType != TrackpadGestureType.TWO_FINGER;
+        for (int i = 0; i < pointerCount; i++) {
+            pointerProperties[i] = getPointerProperties(i);
+            pointerCoords[i] = getPointerCoords(x, y);
+            if (isMultiFingerGesture) {
+                pointerCoords[i].setAxisValue(AXIS_GESTURE_SWIPE_FINGER_COUNT,
+                        gestureType == TrackpadGestureType.THREE_FINGER ? 3 : 4);
+            }
+        }
+        return MotionEvent.obtain(downTime, eventTime, action, pointerCount, pointerProperties,
+                pointerCoords, 0, 0, 1.0f, 1.0f, 0, 0,
+                InputDevice.SOURCE_MOUSE | InputDevice.SOURCE_CLASS_POINTER, 0, 0,
+                isMultiFingerGesture ? MotionEvent.CLASSIFICATION_MULTI_FINGER_SWIPE
+                        : MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE);
+    }
+
     private static MotionEvent getMotionEvent(long downTime, long eventTime, int action,
             float x, float y) {
-        MotionEvent.PointerProperties properties = new MotionEvent.PointerProperties();
-        properties.id = 0;
-        properties.toolType = Configurator.getInstance().getToolType();
+        return MotionEvent.obtain(downTime, eventTime, action, 1,
+                new MotionEvent.PointerProperties[] {getPointerProperties(0)},
+                new MotionEvent.PointerCoords[] {getPointerCoords(x, y)},
+                0, 0, 1.0f, 1.0f, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
+    }
 
+    private static MotionEvent.PointerProperties getPointerProperties(int pointerId) {
+        MotionEvent.PointerProperties properties = new MotionEvent.PointerProperties();
+        properties.id = pointerId;
+        properties.toolType = Configurator.getInstance().getToolType();
+        return properties;
+    }
+
+    private static MotionEvent.PointerCoords getPointerCoords(float x, float y) {
         MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords();
         coords.pressure = 1;
         coords.size = 1;
         coords.x = x;
         coords.y = y;
-
-        return MotionEvent.obtain(downTime, eventTime, action, 1,
-                new MotionEvent.PointerProperties[]{properties},
-                new MotionEvent.PointerCoords[]{coords},
-                0, 0, 1.0f, 1.0f, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
+        return coords;
     }
 
     private boolean hasTIS() {
@@ -1655,17 +1754,26 @@
     public void sendPointer(long downTime, long currentTime, int action, Point point,
             GestureScope gestureScope) {
         final boolean hasTIS = hasTIS();
-        switch (action) {
+        int pointerCount = mPointerCount;
+
+        boolean isTrackpadGesture = mTrackpadGestureType != TrackpadGestureType.NONE;
+        boolean isTwoFingerTrackpadGesture = mTrackpadGestureType == TrackpadGestureType.TWO_FINGER;
+        switch (action & MotionEvent.ACTION_MASK) {
             case MotionEvent.ACTION_DOWN:
                 if (gestureScope != GestureScope.OUTSIDE_WITH_PILFER
                         && gestureScope != GestureScope.OUTSIDE_WITHOUT_PILFER
-                        && gestureScope != GestureScope.OUTSIDE_WITH_KEYCODE) {
+                        && gestureScope != GestureScope.OUTSIDE_WITH_KEYCODE
+                        && (!isTrackpadGesture || isTwoFingerTrackpadGesture)) {
                     expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_TOUCH_DOWN);
                 }
                 if (hasTIS && (isTrackpadGestureEnabled()
                         || getNavigationModel() != NavigationModel.THREE_BUTTON)) {
                     expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_TOUCH_DOWN_TIS);
                 }
+                if (isTrackpadGesture) {
+                    mPointerCount = 1;
+                    pointerCount = mPointerCount;
+                }
                 break;
             case MotionEvent.ACTION_UP:
                 if (hasTIS && gestureScope != GestureScope.INSIDE
@@ -1676,7 +1784,8 @@
                 }
                 if (gestureScope != GestureScope.OUTSIDE_WITH_PILFER
                         && gestureScope != GestureScope.OUTSIDE_WITHOUT_PILFER
-                        && gestureScope != GestureScope.OUTSIDE_WITH_KEYCODE) {
+                        && gestureScope != GestureScope.OUTSIDE_WITH_KEYCODE
+                        && (!isTrackpadGesture || isTwoFingerTrackpadGesture)) {
                     expectEvent(TestProtocol.SEQUENCE_MAIN,
                             gestureScope == GestureScope.INSIDE
                                     || gestureScope == GestureScope.OUTSIDE_WITHOUT_PILFER
@@ -1696,9 +1805,42 @@
             case MotionEvent.ACTION_HOVER_EXIT:
                 expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_HOVER_EXIT_TIS);
                 break;
+            case MotionEvent.ACTION_POINTER_DOWN:
+                mPointerCount++;
+                if (gestureScope != GestureScope.OUTSIDE_WITH_PILFER
+                        && gestureScope != GestureScope.OUTSIDE_WITHOUT_PILFER
+                        && gestureScope != GestureScope.OUTSIDE_WITH_KEYCODE
+                        && (!isTrackpadGesture || isTwoFingerTrackpadGesture)) {
+                    expectEvent(TestProtocol.SEQUENCE_MAIN,
+                            getTouchEventPattern("ACTION_POINTER_DOWN", mPointerCount));
+                }
+                expectEvent(TestProtocol.SEQUENCE_TIS, getTouchEventPatternTIS(
+                        "ACTION_POINTER_DOWN", mPointerCount));
+                pointerCount = mPointerCount;
+                break;
+            case MotionEvent.ACTION_POINTER_UP:
+                if (gestureScope != GestureScope.OUTSIDE_WITH_PILFER
+                        && gestureScope != GestureScope.OUTSIDE_WITHOUT_PILFER
+                        && gestureScope != GestureScope.OUTSIDE_WITH_KEYCODE
+                        && (!isTrackpadGesture || isTwoFingerTrackpadGesture)) {
+                    expectEvent(TestProtocol.SEQUENCE_MAIN,
+                            getTouchEventPattern("ACTION_POINTER_UP", mPointerCount));
+                }
+                // When the gesture is handled outside, it's cancelled within launcher.
+                if (gestureScope != GestureScope.INSIDE_TO_OUTSIDE_WITH_KEYCODE
+                        && gestureScope != GestureScope.OUTSIDE_WITH_KEYCODE) {
+                    expectEvent(TestProtocol.SEQUENCE_TIS, getTouchEventPatternTIS(
+                            "ACTION_POINTER_UP", mPointerCount));
+                }
+                mPointerCount--;
+                break;
         }
 
-        final MotionEvent event = getMotionEvent(downTime, currentTime, action, point.x, point.y);
+        final MotionEvent event = isTrackpadGesture
+                ? getTrackpadMotionEvent(
+                        downTime, currentTime, action, point.x, point.y, pointerCount,
+                        mTrackpadGestureType)
+                : getMotionEvent(downTime, currentTime, action, point.x, point.y);
         assertTrue("injectInputEvent failed",
                 mInstrumentation.getUiAutomation().injectInputEvent(event, true, false));
         event.recycle();
@@ -1706,8 +1848,7 @@
 
     public long movePointer(long downTime, long startTime, long duration, Point from, Point to,
             GestureScope gestureScope) {
-        return movePointer(
-                downTime, startTime, duration, false, from, to, gestureScope);
+        return movePointer(downTime, startTime, duration, false, from, to, gestureScope);
     }
 
     public long movePointer(long downTime, long startTime, long duration, boolean isDecelerating,
@@ -1855,6 +1996,12 @@
         return tasks;
     }
 
+    /** Reinitializes the workspace to its default layout. */
+    public void reinitializeLauncherData() {
+        getTestInfo(TestProtocol.REQUEST_REINITIALIZE_DATA);
+    }
+
+    /** Clears the workspace, leaving it empty. */
     public void clearLauncherData() {
         getTestInfo(TestProtocol.REQUEST_CLEAR_DATA);
     }