Merge "Fix handle color when interrupting the animation" into main
diff --git a/Android.bp b/Android.bp
index 73d0fce..6bd8602 100644
--- a/Android.bp
+++ b/Android.bp
@@ -475,6 +475,9 @@
     ],
     manifest: "quickstep/AndroidManifest.xml",
     min_sdk_version: "current",
+    lint: {
+        disabled_checks: ["MissingClass"],
+    },
 }
 
 // Library with all the source code and dependencies for building Launcher Go
diff --git a/OWNERS b/OWNERS
index 22efa33..bed2acd 100644
--- a/OWNERS
+++ b/OWNERS
@@ -6,10 +6,8 @@
 
 adamcohen@google.com
 hyunyoungs@google.com
-twickham@google.com
 vadimt@google.com
 winsonc@google.com
-jonmiranda@google.com
 awickham@google.com
 agvard@google.com
 
@@ -29,7 +27,6 @@
 peanutbutter@google.com
 jeremysim@google.com
 atsjenk@google.com
-brianji@google.com
 hwwang@google.com
 
 # Overview eng team
@@ -37,6 +34,8 @@
 samcackett@google.com
 silvajordan@google.com
 uwaisashraf@google.com
+vinayjoglekar@google.com
+willosborn@google.com
 
 # Physical Keyboard & Trackpad eng team
 patmanning@google.com
@@ -46,6 +45,16 @@
 shamalip@google.com
 zakcohen@google.com
 
+# System Navigation team
+brianji@google.com
+jonmiranda@google.com
+jagrutdesai@google.com
+randypfohl@google.com
+saumyaprakash@google.com
+sukeshram@google.com
+twickham@google.com
+victortulias@google.com
+
 per-file FeatureFlags.java, globs = set noparent
 per-file FeatureFlags.java = sunnygoyal@google.com, winsonc@google.com, adamcohen@google.com, hyunyoungs@google.com, captaincole@google.com
 
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index bc49146..c3fb150 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -543,4 +543,24 @@
   namespace: "launcher"
   description: "Enable launcher icon shape customizations"
   bug: "348708061"
-}
\ No newline at end of file
+}
+
+flag {
+  name: "predictive_back_to_home_polish"
+  namespace: "launcher"
+  description: "Enables workspace reveal animation for predictive back-to-home"
+  bug: "382453424"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
+  name: "predictive_back_to_home_blur"
+  namespace: "launcher"
+  description: "Adds blur for predictive back-to-home"
+  bug: "342178850"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
diff --git a/go/quickstep/res/values-af/strings.xml b/go/quickstep/res/values-af/strings.xml
index 501d297..f4ce476 100644
--- a/go/quickstep/res/values-af/strings.xml
+++ b/go/quickstep/res/values-af/strings.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_share_drop_target_label" msgid="5804774105974539508">"Deel program"</string>
+    <string name="app_share_drop_target_label" msgid="5804774105974539508">"Deel app"</string>
     <string name="action_listen" msgid="2370304050784689486">"Luister"</string>
     <string name="action_translate" msgid="8028378961867277746">"Vertaal"</string>
     <string name="action_search" msgid="6269564710943755464">"Lens"</string>
@@ -9,12 +9,12 @@
     <string name="dialog_cancel" msgid="6464336969134856366">"KANSELLEER"</string>
     <string name="dialog_settings" msgid="6564397136021186148">"INSTELLINGS"</string>
     <string name="niu_actions_confirmation_title" msgid="3863451714863526143">"Vertaal of luister na teks op skerm"</string>
-    <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"Inligting soos teks op jou skerm, webadresse en skermskote kan met Google gedeel word.\n\nGaan na "<b>"Instellings &gt; Programme &gt; Verstekprogramme &gt; Digitale Assistent-program"</b>" om te verander watter inligting jy deel."</string>
+    <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"Inligting soos teks op jou skerm, webadresse en skermskote kan met Google gedeel word.\n\nGaan na "<b>"Instellings &gt; Apps &gt; Verstekapps &gt; Digitale Assistent-app"</b>" om te verander watter inligting jy deel."</string>
     <string name="assistant_not_selected_title" msgid="5017072974603345228">"Kies \'n assistent om hierdie kenmerk te gebruik"</string>
     <string name="assistant_not_selected_text" msgid="3244613673884359276">"Kies \'n digitalebystandprogram in Instellings om na teks op jou skerm te luister of dit te vertaal"</string>
     <string name="assistant_not_supported_title" msgid="1675788067597484142">"Verander jou assistent om hierdie kenmerk te gebruik"</string>
     <string name="assistant_not_supported_text" msgid="1708031078549268884">"Verander jou digitalebystandprogram in Instellings om na teks op jou skerm te luister of dit te vertaal"</string>
     <string name="tooltip_listen" msgid="7634466447860989102">"Tik hier om na teks op hierdie skerm te luister"</string>
     <string name="tooltip_translate" msgid="4184845868901542567">"Tik hier om teks op hierdie skerm te vertaal"</string>
-    <string name="toast_p2p_app_not_shareable" msgid="7229739094132131536">"Hierdie program kan nie gedeel word nie"</string>
+    <string name="toast_p2p_app_not_shareable" msgid="7229739094132131536">"Hierdie app kan nie gedeel word nie"</string>
 </resources>
diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml
index 8c39585..201c5f6 100644
--- a/quickstep/AndroidManifest.xml
+++ b/quickstep/AndroidManifest.xml
@@ -81,7 +81,7 @@
              android:stateNotNeeded="true"
              android:theme="@style/LauncherTheme"
              android:screenOrientation="behind"
-             android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize"
+             android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize|uiMode"
              android:resizeableActivity="true"
              android:resumeWhilePausing="true"
              android:enableOnBackInvokedCallback="false"
diff --git a/quickstep/dagger/LauncherAppComponent.java b/quickstep/dagger/com/android/launcher3/dagger/LauncherAppComponent.java
similarity index 91%
rename from quickstep/dagger/LauncherAppComponent.java
rename to quickstep/dagger/com/android/launcher3/dagger/LauncherAppComponent.java
index 068f01c..a4cb420 100644
--- a/quickstep/dagger/LauncherAppComponent.java
+++ b/quickstep/dagger/com/android/launcher3/dagger/LauncherAppComponent.java
@@ -17,7 +17,6 @@
 package com.android.launcher3.dagger;
 
 
-import com.android.quickstep.dagger.QuickStepModule;
 import com.android.quickstep.dagger.QuickstepBaseAppComponent;
 
 import dagger.Component;
@@ -26,7 +25,7 @@
  * Root component for Dagger injection for Launcher Quickstep.
  */
 @LauncherAppSingleton
-@Component(modules = QuickStepModule.class)
+@Component(modules = LauncherAppModule.class)
 public interface LauncherAppComponent extends QuickstepBaseAppComponent {
     /** Builder for quickstep LauncherAppComponent. */
     @Component.Builder
diff --git a/quickstep/dagger/com/android/launcher3/dagger/LauncherAppModule.java b/quickstep/dagger/com/android/launcher3/dagger/LauncherAppModule.java
new file mode 100644
index 0000000..1711fc1
--- /dev/null
+++ b/quickstep/dagger/com/android/launcher3/dagger/LauncherAppModule.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.dagger;
+
+import com.android.quickstep.dagger.QuickStepModule;
+
+import dagger.Module;
+
+@Module(includes = QuickStepModule.class)
+public class LauncherAppModule {}
diff --git a/quickstep/res/layout/floating_header_content.xml b/quickstep/res/layout/floating_header_content.xml
index b21c34b..0021e22 100644
--- a/quickstep/res/layout/floating_header_content.xml
+++ b/quickstep/res/layout/floating_header_content.xml
@@ -3,8 +3,8 @@
 
     <com.android.launcher3.appprediction.PredictionRowView
         android:id="@+id/prediction_row"
-        android:accessibilityPaneTitle="@string/title_app_suggestions"
         android:layout_width="match_parent"
+        android:importantForAccessibility="yes"
         android:layout_height="wrap_content" />
 
     <com.android.launcher3.appprediction.AppsDividerView
diff --git a/quickstep/res/layout/task.xml b/quickstep/res/layout/task.xml
index a7f6b36..3aac1b6 100644
--- a/quickstep/res/layout/task.xml
+++ b/quickstep/res/layout/task.xml
@@ -28,7 +28,11 @@
     launcher:focusBorderColor="@color/materialColorOutline"
     launcher:hoverBorderColor="@color/materialColorPrimary">
 
-    <include layout="@layout/task_thumbnail_deprecated" />
+    <ViewStub
+        android:id="@+id/snapshot"
+        android:inflatedId="@id/snapshot"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
 
     <!-- Filtering affects only alpha instead of the visibility since visibility can be altered
          separately through RecentsView#resetFromSplitSelectionState() -->
diff --git a/quickstep/res/layout/task_grouped.xml b/quickstep/res/layout/task_grouped.xml
index 4c650b9..3e6f5ed 100644
--- a/quickstep/res/layout/task_grouped.xml
+++ b/quickstep/res/layout/task_grouped.xml
@@ -33,10 +33,17 @@
     launcher:focusBorderColor="@color/materialColorOutline"
     launcher:hoverBorderColor="@color/materialColorPrimary">
 
-    <include layout="@layout/task_thumbnail_deprecated"/>
+    <ViewStub
+        android:id="@+id/snapshot"
+        android:inflatedId="@id/snapshot"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
 
-    <include layout="@layout/task_thumbnail_deprecated"
-        android:id="@+id/bottomright_snapshot" />
+    <ViewStub
+        android:id="@+id/bottomright_snapshot"
+        android:inflatedId="@id/bottomright_snapshot"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
 
     <!-- Filtering affects only alpha instead of the visibility since visibility can be altered
          separately through RecentsView#resetFromSplitSelectionState() -->
diff --git a/quickstep/res/layout/task_thumbnail.xml b/quickstep/res/layout/task_thumbnail.xml
index afbcdb5..3b96615 100644
--- a/quickstep/res/layout/task_thumbnail.xml
+++ b/quickstep/res/layout/task_thumbnail.xml
@@ -15,7 +15,6 @@
 -->
 <com.android.quickstep.task.thumbnail.TaskThumbnailView
     xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
     android:id="@+id/snapshot"
     android:layout_width="match_parent"
     android:layout_height="match_parent" >
@@ -53,10 +52,7 @@
         android:id="@+id/splash_icon"
         android:layout_width="@dimen/task_thumbnail_splash_icon_size"
         android:layout_height="@dimen/task_thumbnail_splash_icon_size"
-        app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
+        android:layout_gravity="center"
         android:scaleType="fitCenter"
         android:alpha="0"
         android:importantForAccessibility="no" />
diff --git a/quickstep/res/values-af/strings.xml b/quickstep/res/values-af/strings.xml
index 22125d7..365916e 100644
--- a/quickstep/res/values-af/strings.xml
+++ b/quickstep/res/values-af/strings.xml
@@ -46,7 +46,7 @@
     <string name="hotseat_tip_gaps_filled" msgid="3035673010274223538">"Programvoorstelle is in leë spasie bygevoeg"</string>
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Programvoorstelle is geaktiveer"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Programvoorstelle is gedeaktiveer"</string>
-    <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Voorspelde program: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Voorspelde app: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Draai jou toestel"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Draai asseblief jou toestel om die tutoriaal oor gebaarnavigasie te voltooi"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Maak seker dat jy van die rand heel regs of heel links af swiep"</string>
@@ -104,10 +104,10 @@
     <string name="toast_split_select_app_cancel" msgid="1939025102486630426">"Kanselleer"</string>
     <string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Verlaat verdeeldeskermkeuse"</string>
     <string name="toast_split_app_unsupported" msgid="2360229567007828914">"Kies nog ’n app as jy verdeelde skerm wil gebruik"</string>
-    <string name="blocked_by_policy" msgid="2071401072261365546">"Jou organisasie laat nie hierdie program toe nie"</string>
+    <string name="blocked_by_policy" msgid="2071401072261365546">"Jou organisasie laat nie hierdie app toe nie"</string>
     <string name="split_widgets_not_supported" msgid="1355743038053053866">"Legstukke word nie tans ondersteun nie; kies asseblief ’n ander app"</string>
     <string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Slaan navigasietutoriaal oor?"</string>
-    <string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Jy kan dit later in die <xliff:g id="NAME">%1$s</xliff:g>-program kry"</string>
+    <string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Jy kan dit later in die <xliff:g id="NAME">%1$s</xliff:g>-app kry"</string>
     <string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Kanselleer"</string>
     <string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Slaan oor"</string>
     <string name="accessibility_rotate_button" msgid="4771825231336502943">"Draai skerm"</string>
diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml
index e8c8505..1f33e08 100644
--- a/quickstep/res/values/config.xml
+++ b/quickstep/res/values/config.xml
@@ -23,7 +23,6 @@
 
     <string name="stats_log_manager_class" translatable="false">com.android.quickstep.logging.StatsLogCompatManager</string>
     <string name="test_information_handler_class" translatable="false">com.android.quickstep.QuickstepTestInformationHandler</string>
-    <string name="window_manager_proxy_class" translatable="false">com.android.quickstep.util.SystemWindowManagerProxy</string>
     <string name="widget_holder_factory_class" translatable="false">com.android.launcher3.uioverrides.QuickstepWidgetHolder$QuickstepHolderFactory</string>
     <string name="instant_app_resolver_class" translatable="false">com.android.quickstep.InstantAppResolverImpl</string>
     <string name="app_launch_tracker_class" translatable="false">com.android.launcher3.appprediction.PredictionAppTracker</string>
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index 026e25c..df949c3 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -125,8 +125,6 @@
     <string name="back_gesture_intro_title">Swipe to go back</string>
     <!-- Introduction subtitle for the Back gesture tutorial. [CHAR LIMIT=200] -->
     <string name="back_gesture_intro_subtitle">To go back to the last screen, swipe from the left or right edge to the middle of the screen.</string>
-    <!-- Introduction subtitle for the Back gesture tutorial that will be spoken by screen readers. [CHAR LIMIT=200] -->
-    <string name="back_gesture_spoken_intro_subtitle">To go back to the last screen, swipe with 2 fingers from the left or right edge to the middle of the screen.</string>
     <!-- Title of the gesture tutorial section educating users on how to go back to the previous screen. [CHAR LIMIT=100] -->
     <string name="back_gesture_tutorial_title">Go back</string>
     <!-- Subtitle of the gesture tutorial section educating users on how to go to back to the previous screen [CHAR LIMIT=100] -->
@@ -145,8 +143,6 @@
     <string name="home_gesture_intro_title">Swipe to go home</string>
     <!-- Introduction subtitle for the Home gesture tutorial. [CHAR LIMIT=100] -->
     <string name="home_gesture_intro_subtitle">Swipe up from the bottom of your screen. This gesture always takes you to the Home screen.</string>
-    <!-- Introduction subtitle for the Home gesture tutorial that will be spoken by screen readers. [CHAR LIMIT=100] -->
-    <string name="home_gesture_spoken_intro_subtitle">Swipe up with 2 fingers from the bottom of the screen. This gesture always takes you to the Home screen.</string>
     <!-- Title of the gesture tutorial section educating users on how to go to the home screen. [CHAR LIMIT=100] -->
     <string name="home_gesture_tutorial_title">Go home</string>
     <!-- Subtitle of the gesture tutorial section educating users on how to go to the home screen [CHAR LIMIT=100] -->
@@ -168,8 +164,6 @@
     <string name="overview_gesture_intro_title">Swipe to switch apps</string>
     <!-- Introduction subtitle for the Overview gesture tutorial. [CHAR LIMIT=100] -->
     <string name="overview_gesture_intro_subtitle">To switch between apps, swipe up from the bottom of your screen, hold, then release.</string>
-    <!-- Introduction subtitle for the Overview gesture tutorial that will be spoken by screen readers. [CHAR LIMIT=100] -->
-    <string name="overview_gesture_spoken_intro_subtitle">To switch between apps, swipe up with 2 fingers from the bottom of your screen, hold, then release.</string>
     <!-- Title of the gesture tutorial section educating users on how to switch between apps. [CHAR LIMIT=100] -->
     <string name="overview_gesture_tutorial_title">Switch apps</string>
     <!-- Subtitle of the gesture tutorial section educating users on how to switch between apps [CHAR LIMIT=100] -->
diff --git a/quickstep/src/com/android/launcher3/QuickstepAccessibilityDelegate.java b/quickstep/src/com/android/launcher3/QuickstepAccessibilityDelegate.java
index 1161720..08ef8fe 100644
--- a/quickstep/src/com/android/launcher3/QuickstepAccessibilityDelegate.java
+++ b/quickstep/src/com/android/launcher3/QuickstepAccessibilityDelegate.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3;
 
+import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED;
+
 import static androidx.recyclerview.widget.RecyclerView.NO_POSITION;
 
 import android.view.KeyEvent;
@@ -48,7 +50,11 @@
     public void onPopulateAccessibilityEvent(View view, AccessibilityEvent event) {
         super.onPopulateAccessibilityEvent(view, event);
         // Scroll to the position if focused view in main allapps list and not completely visible.
-        scrollToPositionIfNeeded(view);
+        // Gate based on TYPE_VIEW_ACCESSIBILITY_FOCUSED for unintended scrolling with external
+        // mouse.
+        if (event.getEventType() == TYPE_VIEW_ACCESSIBILITY_FOCUSED) {
+            scrollToPositionIfNeeded(view);
+        }
     }
 
     private void scrollToPositionIfNeeded(View view) {
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 2759816..f38693d 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -1670,7 +1670,10 @@
                 || mLauncher.getWorkspace().isOverlayShown()
                 || shouldPlayFallbackClosingAnimation(appTargets);
 
-        boolean playWorkspaceReveal = !fromPredictiveBack;
+        boolean playWorkspaceReveal = true;
+        if (!Flags.predictiveBackToHomePolish()) {
+            playWorkspaceReveal = !fromPredictiveBack;
+        }
         boolean skipAllAppsScale = false;
         if (!playFallBackAnimation) {
             PointF velocity;
@@ -1689,12 +1692,12 @@
                 // Skip scaling all apps, otherwise FloatingIconView will get wrong
                 // layout bounds.
                 skipAllAppsScale = true;
-            } else if (!fromPredictiveBack) {
+            } else if (Flags.predictiveBackToHomePolish() || !fromPredictiveBack) {
                 if (enableScalingRevealHomeAnimation()) {
                     anim.play(
-                            new ScalingWorkspaceRevealAnim(
-                                    mLauncher, rectFSpringAnim,
-                                    rectFSpringAnim.getTargetRect()).getAnimators());
+                            new ScalingWorkspaceRevealAnim(mLauncher, rectFSpringAnim,
+                                    rectFSpringAnim.getTargetRect(),
+                                    !fromPredictiveBack /* playAlphaReveal */).getAnimators());
                 } else {
                     anim.play(new StaggeredWorkspaceAnim(mLauncher, velocity.y,
                             true /* animateOverviewScrim */, launcherView).getAnimators());
@@ -1713,15 +1716,7 @@
             anim.play(getFallbackClosingWindowAnimators(appTargets));
         }
 
-        // Normally, we run the launcher content animation when we are transitioning
-        // home, but if home is already visible, then we don't want to animate the
-        // contents of launcher unless we know that we are animating home as a result
-        // of the home button press with quickstep, which will result in launcher being
-        // started on touch down, prior to the animation home (and won't be in the
-        // targets list because it is already visible). In that case, we force
-        // invisibility on touch down, and only reset it after the animation to home
-        // is initialized.
-        if (launcherIsForceInvisibleOrOpening || fromPredictiveBack) {
+        if (Flags.predictiveBackToHomePolish()) {
             AnimatorListenerAdapter endListener = new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
@@ -1730,7 +1725,24 @@
                             mLauncher, WALLPAPER_OPEN_ANIMATION_FINISHED_MESSAGE);
                 }
             };
+            if (rectFSpringAnim != null) {
+                rectFSpringAnim.addAnimatorListener(endListener);
+            } else {
+                anim.addListener(endListener);
+            }
+        }
 
+        // Normally, we run the launcher content animation when we are transitioning
+        // home, but if home is already visible, then we don't want to animate the
+        // contents of launcher unless we know that we are animating home as a result
+        // of the home button press with quickstep, which will result in launcher being
+        // started on touch down, prior to the animation home (and won't be in the
+        // targets list because it is already visible). In that case, we force
+        // invisibility on touch down, and only reset it after the animation to home
+        // is initialized.
+        boolean legacyFromPredictiveBack =
+                !Flags.predictiveBackToHomePolish() && fromPredictiveBack;
+        if (launcherIsForceInvisibleOrOpening || legacyFromPredictiveBack) {
             if (rectFSpringAnim != null && anim.getChildAnimations().isEmpty()) {
                 addCujInstrumentation(rectFSpringAnim, Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME);
             } else {
@@ -1738,17 +1750,26 @@
                         ? Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK
                         : Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME);
             }
-
-            if (fromPredictiveBack && rectFSpringAnim != null) {
-                rectFSpringAnim.addAnimatorListener(endListener);
-            } else {
-                anim.addListener(endListener);
+            if (!Flags.predictiveBackToHomePolish()) {
+                AnimatorListenerAdapter endListener = new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        super.onAnimationEnd(animation);
+                        AccessibilityManagerCompat.sendTestProtocolEventToTest(
+                                mLauncher, WALLPAPER_OPEN_ANIMATION_FINISHED_MESSAGE);
+                    }
+                };
+                if (fromPredictiveBack && rectFSpringAnim != null) {
+                    rectFSpringAnim.addAnimatorListener(endListener);
+                } else {
+                    anim.addListener(endListener);
+                }
             }
 
             // Only register the content animation for cancellation when state changes
             mLauncher.getStateManager().setCurrentAnimation(anim);
 
-            if (mLauncher.isInState(LauncherState.ALL_APPS) && !fromPredictiveBack) {
+            if (mLauncher.isInState(LauncherState.ALL_APPS) && !legacyFromPredictiveBack) {
                 Pair<AnimatorSet, Runnable> contentAnimator =
                         getLauncherContentAnimator(false, LAUNCHER_RESUME_START_DELAY,
                                 skipAllAppsScale);
diff --git a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
index 8e80aa5..22e491f 100644
--- a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
+++ b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
@@ -16,12 +16,16 @@
 
 package com.android.launcher3.appprediction;
 
+import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
+
 import android.content.Context;
 import android.graphics.Canvas;
+import android.os.Build;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.LinearLayout;
 
 import androidx.annotation.NonNull;
@@ -91,6 +95,14 @@
     }
 
     @Override
+    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+        super.onInitializeAccessibilityNodeInfo(info);
+        if (Build.VERSION.SDK_INT >= UPSIDE_DOWN_CAKE) {
+            info.setContainerTitle(mActivityContext.getString(R.string.title_app_suggestions));
+        }
+    }
+
+    @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
         mActivityContext.addOnDeviceProfileChangeListener(this);
diff --git a/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransitionManager.kt b/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransitionManager.kt
index 645bef6..6e36305 100644
--- a/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransitionManager.kt
+++ b/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransitionManager.kt
@@ -45,7 +45,8 @@
         }
         remoteWindowLimitUnminimizeTransition =
             RemoteTransition(
-                DesktopAppLaunchTransition(context, MAIN_EXECUTOR, AppLaunchType.UNMINIMIZE)
+                DesktopAppLaunchTransition(context, MAIN_EXECUTOR, AppLaunchType.UNMINIMIZE),
+                "DesktopWindowLimitUnminimize"
             )
         systemUiProxy.registerRemoteTransition(
             remoteWindowLimitUnminimizeTransition,
diff --git a/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt b/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt
index ac1ffa6..8b064d3 100644
--- a/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt
+++ b/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt
@@ -31,6 +31,8 @@
 import com.android.quickstep.SystemUiProxy
 import com.android.quickstep.TaskViewUtils
 import com.android.quickstep.views.DesktopTaskView
+import com.android.quickstep.views.TaskContainer
+import com.android.quickstep.views.TaskView
 import com.android.window.flags.Flags
 import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource
 import java.util.function.Consumer
@@ -62,8 +64,12 @@
     }
 
     /** Launch desktop tasks from recents view */
-    fun moveToDesktop(taskId: Int, transitionSource: DesktopModeTransitionSource) {
-        systemUiProxy.moveToDesktop(taskId, transitionSource)
+    fun moveToDesktop(taskContainer: TaskContainer, transitionSource: DesktopModeTransitionSource) {
+        systemUiProxy.moveToDesktop(
+            taskContainer.task.key.id,
+            transitionSource,
+            /* transition = */ null,
+        )
     }
 
     /** Move task to external display from recents view */
@@ -72,7 +78,7 @@
     }
 
     private class RemoteDesktopLaunchTransitionRunner(
-        private val desktopTaskView: DesktopTaskView,
+        private val taskView: TaskView,
         private val animated: Boolean,
         private val stateManager: StateManager<*, *>,
         private val depthController: DepthController?,
@@ -99,7 +105,7 @@
             MAIN_EXECUTOR.execute {
                 val animator =
                     TaskViewUtils.composeRecentsDesktopLaunchAnimator(
-                        desktopTaskView,
+                        taskView,
                         stateManager,
                         depthController,
                         info,
diff --git a/quickstep/src/com/android/launcher3/model/PredictionUpdateTask.java b/quickstep/src/com/android/launcher3/model/PredictionUpdateTask.java
index d604742..cd08897 100644
--- a/quickstep/src/com/android/launcher3/model/PredictionUpdateTask.java
+++ b/quickstep/src/com/android/launcher3/model/PredictionUpdateTask.java
@@ -18,6 +18,7 @@
 import static com.android.launcher3.EncryptionType.ENCRYPTED;
 import static com.android.launcher3.LauncherPrefs.nonRestorableItem;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
+import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG;
 import static com.android.quickstep.InstantAppResolverImpl.COMPONENT_CLASS_MARKER;
 
 import android.app.prediction.AppTarget;
@@ -95,7 +96,7 @@
                 itemInfo = apps.data.stream()
                         .filter(info -> user.equals(info.user) && cn.equals(info.componentName))
                         .map(ai -> {
-                            app.getIconCache().getTitleAndIcon(ai, false);
+                            app.getIconCache().getTitleAndIcon(ai, DEFAULT_LOOKUP_FLAG);
                             return ai.makeWorkspaceItem(context);
                         })
                         .findAny()
@@ -106,7 +107,7 @@
                                 return null;
                             }
                             AppInfo ai = new AppInfo(context, lai, user);
-                            app.getIconCache().getTitleAndIcon(ai, lai, false);
+                            app.getIconCache().getTitleAndIcon(ai, lai, DEFAULT_LOOKUP_FLAG);
                             return ai.makeWorkspaceItem(context);
                         });
 
diff --git a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
index 2f4c6f6..daba0dd 100644
--- a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
+++ b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
@@ -26,6 +26,7 @@
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
 import static com.android.launcher3.hybridhotseat.HotseatPredictionModel.convertDataModelToAppTargetBundle;
+import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG;
 import static com.android.launcher3.model.PredictionHelper.getAppTargetFromItemInfo;
 import static com.android.launcher3.model.PredictionHelper.wrapAppTargetWithItemLocation;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
@@ -572,7 +573,7 @@
                             mPmHelper,
                             mUMS.isUserQuiet(user));
                     info.container = mContainer;
-                    mAppState.getIconCache().getTitleAndIcon(info, lai, false);
+                    mAppState.getIconCache().getTitleAndIcon(info, lai, DEFAULT_LOOKUP_FLAG);
                     mReadCount++;
                     return info.makeWorkspaceItem(mAppState.getContext());
                 }
diff --git a/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
index 2ff9b18..fd0243a 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
@@ -111,9 +111,10 @@
     public boolean areDesktopTasksVisible() {
         boolean desktopTasksVisible = mVisibleDesktopTasksCount > 0;
         if (DEBUG) {
-            Log.d(TAG, "areDesktopTasksVisible: desktopVisible=" + desktopTasksVisible);
+            Log.d(TAG, "areDesktopTasksVisible: desktopVisible=" + desktopTasksVisible
+                    + " overview=" + mInOverviewState);
         }
-        return desktopTasksVisible;
+        return desktopTasksVisible && !mInOverviewState;
     }
 
     /**
@@ -218,8 +219,12 @@
                     + " currentValue=" + mInOverviewState);
         }
         if (overviewStateEnabled != mInOverviewState) {
+            final boolean wereDesktopTasksVisibleBefore = areDesktopTasksVisible();
             mInOverviewState = overviewStateEnabled;
             final boolean areDesktopTasksVisibleNow = areDesktopTasksVisible();
+            if (wereDesktopTasksVisibleBefore != areDesktopTasksVisibleNow) {
+                notifyDesktopVisibilityListeners(areDesktopTasksVisibleNow);
+            }
 
             if (ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue()) {
                 return;
diff --git a/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java
index 6a908ca..d6327bc 100644
--- a/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java
@@ -28,7 +28,6 @@
 import com.android.launcher3.statemanager.StatefulContainer;
 import com.android.quickstep.TopTaskTracker;
 import com.android.quickstep.fallback.RecentsState;
-import com.android.quickstep.util.TISBindHelper;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.RecentsViewContainer;
 
@@ -139,12 +138,6 @@
         return topTask.isHomeTask() || topTask.isRecentsTask();
     }
 
-    @Nullable
-    @Override
-    protected TISBindHelper getTISBindHelper() {
-        return mRecentsContainer.getTISBindHelper();
-    }
-
     @Override
     protected String getTaskbarUIControllerName() {
         return "FallbackTaskbarUIController<" + mRecentsContainer.getClass().getSimpleName() + ">";
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
index 3114bc8..cb811d6 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
@@ -15,6 +15,7 @@
  */
 package com.android.launcher3.taskbar;
 
+import static com.android.launcher3.desktop.DesktopAppLaunchTransition.AppLaunchType.UNMINIMIZE;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 
@@ -285,8 +286,8 @@
         ) {
             // This app is being unminimized - use our own transition runner.
             remoteTransition = new RemoteTransition(
-                    new DesktopAppLaunchTransition(
-                        context, MAIN_EXECUTOR, AppLaunchType.UNMINIMIZE));
+                    new DesktopAppLaunchTransition(context, MAIN_EXECUTOR, UNMINIMIZE),
+                    "DesktopKeyboardQuickSwitchUnminimize");
         }
         mControllers.taskbarActivityContext.handleGroupTaskLaunch(
                 task,
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 7d75286..a6eee08 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -49,7 +49,6 @@
 import com.android.quickstep.RecentsAnimationCallbacks;
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.util.GroupTask;
-import com.android.quickstep.util.TISBindHelper;
 import com.android.quickstep.views.RecentsView;
 import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
 import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
@@ -217,8 +216,10 @@
 
     private int getTaskbarAnimationDuration(boolean isVisible) {
         // fast animation duration since we will not be playing workspace reveal animation.
-        boolean shouldOverrideToFastAnimation =
-                !isHotseatIconOnTopWhenAligned() || mLauncher.getPredictiveBackToHomeInProgress();
+        boolean shouldOverrideToFastAnimation = !isHotseatIconOnTopWhenAligned();
+        if (!Flags.predictiveBackToHomePolish()) {
+            shouldOverrideToFastAnimation |= mLauncher.getPredictiveBackToHomeInProgress();
+        }
         boolean isPinnedTaskbar = DisplayController.isPinnedTaskbar(mLauncher);
         if (isVisible || isPinnedTaskbar) {
             return getTaskbarToHomeDuration(shouldOverrideToFastAnimation, isPinnedTaskbar);
@@ -483,12 +484,6 @@
         mTaskbarLauncherStateController.resetIconAlignment();
     }
 
-    @Nullable
-    @Override
-    protected TISBindHelper getTISBindHelper() {
-        return mLauncher.getTISBindHelper();
-    }
-
     @Override
     public void dumpLogs(String prefix, PrintWriter pw) {
         super.dumpLogs(prefix, pw);
diff --git a/quickstep/src/com/android/launcher3/taskbar/ManageWindowsTaskbarShortcut.kt b/quickstep/src/com/android/launcher3/taskbar/ManageWindowsTaskbarShortcut.kt
index 032eb51..75ce7c3 100644
--- a/quickstep/src/com/android/launcher3/taskbar/ManageWindowsTaskbarShortcut.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/ManageWindowsTaskbarShortcut.kt
@@ -148,11 +148,19 @@
                         FLAG_AUTOHIDE_SUSPEND_MULTI_INSTANCE_MENU_OPEN,
                         false,
                     )
+                    controllers.taskbarPopupController.cleanUpMultiInstanceMenuReference()
                 }
             }
         )
     }
 
+    /** Closes the multi-instance menu if it has been initialized. */
+    fun closeMultiInstanceMenu() {
+        if (::taskbarShortcutAllWindowsView.isInitialized) {
+            taskbarShortcutAllWindowsView.animateClose()
+        }
+    }
+
     /**
      * A view container for displaying the window of open instances of an app
      *
@@ -238,6 +246,7 @@
             )
             taskbarOverlayContext.dragLayer?.removeView(menuView.rootView)
             taskbarOverlayContext.dragLayer.removeTouchController(this)
+            controllers.taskbarPopupController.cleanUpMultiInstanceMenuReference()
         }
 
         /** TouchController implementations for closing the carousel when touched outside */
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index cb4e5e2..1144ac5 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -51,7 +51,6 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SHORTCUT_HELPER_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING;
 import static com.android.window.flags.Flags.predictiveBackThreeButtonNav;
-import static com.android.wm.shell.Flags.enableBubbleBarInPersistentTaskBar;
 
 import android.animation.Animator;
 import android.animation.ArgbEvaluator;
@@ -866,13 +865,19 @@
             TaskbarNavButtonController navButtonController) {
         final RectF rect = new RectF();
         buttonView.setOnTouchListener((v, event) -> {
-            if (event.getAction() == MotionEvent.ACTION_DOWN) {
+            int motionEventAction = event.getAction();
+            if (motionEventAction == MotionEvent.ACTION_DOWN) {
                 rect.set(0, 0, v.getWidth(), v.getHeight());
             }
-            boolean isCancelled = event.getAction() == MotionEvent.ACTION_CANCEL
-                    || !rect.contains(event.getX(), event.getY());
-            if (event.getAction() == MotionEvent.ACTION_MOVE && !isCancelled) return false;
-            int motionEventAction = event.getAction();
+            boolean isCancelled = motionEventAction == MotionEvent.ACTION_CANCEL
+                    || (!rect.contains(event.getX(), event.getY())
+                    && (motionEventAction == MotionEvent.ACTION_MOVE
+                    || motionEventAction == MotionEvent.ACTION_UP));
+            if (motionEventAction != MotionEvent.ACTION_DOWN
+                    && motionEventAction != MotionEvent.ACTION_UP && !isCancelled) {
+                // return early. we don't care about any other cases than DOWN, UP and CANCEL
+                return false;
+            }
             int keyEventAction = motionEventAction == MotionEvent.ACTION_DOWN
                     ? KeyEvent.ACTION_DOWN : ACTION_UP;
             navButtonController.sendBackKeyEvent(keyEventAction, isCancelled);
@@ -1342,8 +1347,7 @@
                 || mNavButtonsView.getWidth() == 0) {
             return;
         }
-        if (enableBubbleBarInPersistentTaskBar()
-                && mControllers.bubbleControllers.isPresent()) {
+        if (mControllers.bubbleControllers.isPresent()) {
             if (mBubbleBarTargetLocation == null) {
                 // only set bubble bar location if it was not set before
                 mBubbleBarTargetLocation = mControllers.bubbleControllers.get()
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 70cb7ce..933eb96 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -163,7 +163,6 @@
 import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
 import com.android.systemui.unfold.updates.RotationChangeProvider;
 import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider;
-import com.android.wm.shell.Flags;
 
 import java.io.PrintWriter;
 import java.util.Collections;
@@ -277,12 +276,8 @@
         TaskbarScrimView taskbarScrimView = mDragLayer.findViewById(R.id.taskbar_scrim);
         NearestTouchFrame navButtonsView = mDragLayer.findViewById(R.id.navbuttons_view);
         StashedHandleView stashedHandleView = mDragLayer.findViewById(R.id.stashed_handle);
-        BubbleBarView bubbleBarView = null;
-        FrameLayout bubbleBarContainer = null;
-        if (isTransientTaskbar || Flags.enableBubbleBarInPersistentTaskBar()) {
-            bubbleBarView = mDragLayer.findViewById(R.id.taskbar_bubbles);
-            bubbleBarContainer = mDragLayer.findViewById(R.id.taskbar_bubbles_container);
-        }
+        BubbleBarView bubbleBarView = mDragLayer.findViewById(R.id.taskbar_bubbles);
+        FrameLayout bubbleBarContainer = mDragLayer.findViewById(R.id.taskbar_bubbles_container);
         StashedHandleView bubbleHandleView = mDragLayer.findViewById(R.id.stashed_bubble_handle);
 
         mAccessibilityDelegate = new TaskbarShortcutMenuAccessibilityDelegate(this);
@@ -907,7 +902,8 @@
         ActivityOptions options = ActivityOptions.makeRemoteTransition(
                 new RemoteTransition(
                         new DesktopAppLaunchTransition(
-                                /* context= */ this, getMainExecutor(), launchType)));
+                                /* context= */ this, getMainExecutor(), launchType),
+                        "TaskbarDesktopLaunch"));
         return new ActivityOptionsWrapper(options, new RunnableList());
     }
 
@@ -1321,25 +1317,9 @@
         } else if (tag instanceof TaskItemInfo info) {
             RemoteTransition remoteTransition = canUnminimizeDesktopTask(info.getTaskId())
                     ? createUnminimizeRemoteTransition() : null;
-
-            if (areDesktopTasksVisible() && recents != null) {
-                TaskView taskView = recents.getTaskViewByTaskId(info.getTaskId());
-                if (taskView == null) return;
-                RunnableList runnableList = taskView.launchWithAnimation();
-                if (runnableList != null) {
-                    runnableList.add(() ->
-                            // wrapped it in runnable here since we need the post for DW to be
-                            // ready. if we don't other DW will be gone and only the launched task
-                            // will show.
-                            UI_HELPER_EXECUTOR.execute(() ->
-                                    SystemUiProxy.INSTANCE.get(this).showDesktopApp(
-                                            info.getTaskId(), remoteTransition)));
-                }
-            } else {
-                UI_HELPER_EXECUTOR.execute(() ->
-                        SystemUiProxy.INSTANCE.get(this).showDesktopApp(
-                                info.getTaskId(), remoteTransition));
-            }
+            UI_HELPER_EXECUTOR.execute(() ->
+                    SystemUiProxy.INSTANCE.get(this).showDesktopApp(
+                            info.getTaskId(), remoteTransition));
             mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(
                     /* stash= */ true);
         } else if (tag instanceof WorkspaceItemInfo) {
@@ -1421,6 +1401,7 @@
             Log.e(TAG, "Unknown type clicked: " + tag);
         }
 
+        mControllers.taskbarPopupController.maybeCloseMultiInstanceMenu();
         if (shouldCloseAllOpenViews) {
             AbstractFloatingView.closeAllOpenViews(this);
         }
@@ -1497,8 +1478,8 @@
 
     private RemoteTransition createUnminimizeRemoteTransition() {
         return new RemoteTransition(
-                new DesktopAppLaunchTransition(
-                        this, getMainExecutor(), AppLaunchType.UNMINIMIZE));
+                new DesktopAppLaunchTransition(this, getMainExecutor(), AppLaunchType.UNMINIMIZE),
+                "TaskbarDesktopUnminimize");
     }
 
     /**
@@ -1583,17 +1564,7 @@
                                                 .launchAppPair((AppPairIcon) launchingIconView,
                                                         -1 /*cuj*/)));
                     } else {
-                        if (areDesktopTasksVisible()) {
-                            RunnableList runnableList = recents.launchDesktopTaskView();
-                            // Wrapping it in runnable so we post after DW is ready for the app
-                            // launch.
-                            if (runnableList != null) {
-                                runnableList.add(() -> UI_HELPER_EXECUTOR.execute(
-                                        () -> startItemInfoActivity(itemInfos.get(0), foundTask)));
-                            }
-                        } else {
-                            startItemInfoActivity(itemInfos.get(0), foundTask);
-                        }
+                        startItemInfoActivity(itemInfos.get(0), foundTask);
                     }
                 }
         );
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index 9407e73..3e9a073 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -47,6 +47,7 @@
 import android.provider.Settings;
 import android.util.Log;
 import android.util.SparseArray;
+import android.util.SparseBooleanArray;
 import android.view.Display;
 import android.view.MotionEvent;
 import android.view.WindowManager;
@@ -116,7 +117,6 @@
     private final Context mWindowContext;
     private final @Nullable Context mNavigationBarPanelContext;
     private WindowManager mWindowManager;
-    private boolean mAddedWindow;
     private final TaskbarNavButtonController mDefaultNavButtonController;
     private final ComponentCallbacks mDefaultComponentCallbacks;
 
@@ -133,6 +133,8 @@
     private final SparseArray<TaskbarActivityContext> mTaskbars = new SparseArray<>();
     /** DisplayId - {@link FrameLayout} map for Connected Display. */
     private final SparseArray<FrameLayout> mRootLayouts = new SparseArray<>();
+    /** DisplayId - {@link Boolean} map indicating if RootLayout was added to window. */
+    private final SparseBooleanArray mAddedRootLayouts = new SparseBooleanArray();
     private StatefulActivity mActivity;
     private RecentsViewContainer mRecentsViewContainer;
 
@@ -742,23 +744,42 @@
 
     private void addTaskbarRootViewToWindow(int displayId) {
         TaskbarActivityContext taskbar = getTaskbarForDisplay(displayId);
-        if (enableTaskbarNoRecreate() && !mAddedWindow && taskbar != null) {
+        if (!enableTaskbarNoRecreate() || taskbar == null) {
+            return;
+        }
+
+        if (!isTaskbarRootLayoutAddedForDisplay(displayId)) {
             mWindowManager.addView(getTaskbarRootLayoutForDisplay(displayId),
                     taskbar.getWindowLayoutParams());
-            mAddedWindow = true;
+            mAddedRootLayouts.put(displayId, true);
         }
     }
 
     private void removeTaskbarRootViewFromWindow(int displayId) {
         FrameLayout rootLayout = getTaskbarRootLayoutForDisplay(displayId);
-        if (enableTaskbarNoRecreate() && mAddedWindow && rootLayout != null) {
+        if (!enableTaskbarNoRecreate() || rootLayout == null) {
+            return;
+        }
+
+        if (isTaskbarRootLayoutAddedForDisplay(displayId)) {
             mWindowManager.removeViewImmediate(rootLayout);
-            mAddedWindow = false;
+            mAddedRootLayouts.put(displayId, false);
             removeTaskbarRootLayoutFromMap(displayId);
         }
     }
 
     /**
+     * Retrieves whether RootLayout was added to window for specific display, or false if no
+     * such mapping has been made.
+     *
+     * @param displayId The ID of the display for which to retrieve the taskbar root layout.
+     * @return if RootLayout was added to window {@link Boolean} for a display or {@code false}.
+     */
+    private boolean isTaskbarRootLayoutAddedForDisplay(int displayId) {
+        return mAddedRootLayouts.get(displayId);
+    }
+
+    /**
      * Returns the {@link TaskbarActivityContext} associated with the given display ID.
      *
      * @param displayId The ID of the display to retrieve the taskbar for.
@@ -853,6 +874,7 @@
      */
     private void removeTaskbarRootLayoutFromMap(int displayId) {
         if (mRootLayouts.contains(displayId)) {
+            mAddedRootLayouts.delete(displayId);
             mRootLayouts.delete(displayId);
         }
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
index 4881836..d1f9be0 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
@@ -344,6 +344,10 @@
         mCallbacks.onToggleOverview();
     }
 
+    public void hideOverview() {
+        mCallbacks.onHideOverview();
+    }
+
     void sendBackKeyEvent(int action, boolean cancelled) {
         if (action == mLastSentBackAction) {
             // There must always be an alternating sequence of ACTION_DOWN and ACTION_UP events
@@ -411,5 +415,8 @@
 
         /** Callback invoked when the overview button is pressed. */
         default void onToggleOverview() {}
+
+        /** Callback invoken when a visible overview needs to be hidden. */
+        default void onHideOverview() { }
     }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarOverflowView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarOverflowView.java
index 8775766..0ed6669 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarOverflowView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarOverflowView.java
@@ -35,6 +35,7 @@
 import android.widget.FrameLayout;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
 import androidx.core.graphics.ColorUtils;
 
 import com.android.app.animation.Interpolators;
@@ -315,6 +316,11 @@
         invalidate();
     }
 
+    @VisibleForTesting
+    public List<Integer> getItemIds() {
+        return mItems.stream().map(task -> task.key.id).toList();
+    }
+
     /**
      * Called when a task is updated. If the task is contained within the view, it's cached value
      * gets updated. If the task is shown within the icon, invalidates the view, so the task icon
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
index abf35a2..6789824 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
@@ -17,6 +17,7 @@
 
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_ALL_APPS;
 import static com.android.launcher3.model.data.AppInfo.COMPONENT_KEY_COMPARATOR;
+import static com.android.launcher3.popup.SystemShortcut.PIN_UNPIN_ITEM;
 import static com.android.launcher3.util.SplitConfigurationOptions.getLogEventForPosition;
 
 import android.content.Intent;
@@ -86,6 +87,8 @@
     private TaskbarControllers mControllers;
     private boolean mAllowInitialSplitSelection;
     private AppInfo[] mAppInfosList;
+    private ManageWindowsTaskbarShortcut<BaseTaskbarContext> mManageWindowsTaskbarShortcut;
+
 
     public TaskbarPopupController(TaskbarActivityContext context) {
         mContext = context;
@@ -111,6 +114,19 @@
         mPopupDataProvider.setDeepShortcutMap(deepShortcutMapCopy);
     }
 
+    /** Closes the multi-instance menu if it is enabled and currently open. */
+    public void maybeCloseMultiInstanceMenu() {
+        if (Flags.enableMultiInstanceMenuTaskbar() && mManageWindowsTaskbarShortcut != null) {
+            mManageWindowsTaskbarShortcut.closeMultiInstanceMenu();
+            cleanUpMultiInstanceMenuReference();
+        }
+    }
+
+    /** Releases the reference to the Taskbar multi-instance menu */
+    public void cleanUpMultiInstanceMenuReference() {
+        mManageWindowsTaskbarShortcut = null;
+    }
+
     public void setAllowInitialSplitSelection(boolean allowInitialSplitSelection) {
         mAllowInitialSplitSelection = allowInitialSplitSelection;
     }
@@ -195,6 +211,9 @@
         // append split options to APP_INFO shortcut if not in Desktop Windowing mode, the order
         // here will reflect in the popup
         ArrayList<SystemShortcut.Factory> shortcuts = new ArrayList<>();
+        if (Flags.enablePinningAppWithContextMenu()) {
+            shortcuts.add(PIN_UNPIN_ITEM);
+        }
         shortcuts.add(APP_INFO);
         if (!mControllers.taskbarDesktopModeController.getAreDesktopTasksVisible()) {
             shortcuts.addAll(mControllers.uiController.getSplitMenuOptions().toList());
@@ -206,6 +225,7 @@
         if (Flags.enableMultiInstanceMenuTaskbar()
                 && DesktopModeStatus.canEnterDesktopMode(mContext)
                 && !mControllers.taskbarStashController.isInOverview()) {
+            maybeCloseMultiInstanceMenu();
             shortcuts.addAll(getMultiInstanceMenuOptions().toList());
         }
         return shortcuts.stream();
@@ -325,8 +345,9 @@
     public SystemShortcut.Factory<BaseTaskbarContext> createManageWindowsShortcutFactory() {
         return (context, itemInfo, originalView) -> {
             if (shouldShowMultiInstanceOptions(itemInfo)) {
-                return new ManageWindowsTaskbarShortcut<>(context, itemInfo, originalView,
-                        mControllers);
+                mManageWindowsTaskbarShortcut = new ManageWindowsTaskbarShortcut<>(
+                        context, itemInfo, originalView, mControllers);
+                return mManageWindowsTaskbarShortcut;
             }
             return null;
         };
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarSharedState.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarSharedState.java
index a64dab1..f4a7d68 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarSharedState.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarSharedState.java
@@ -77,6 +77,8 @@
 
     public List<BubbleInfo> bubbleInfoItems;
 
+    public List<BubbleInfo> suppressedBubbleInfoItems;
+
     /** Returns whether there are a saved bubbles. */
     public boolean hasSavedBubbles() {
         return bubbleInfoItems != null && !bubbleInfoItems.isEmpty();
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index 8b636dd..f29f95d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -39,10 +39,7 @@
 import com.android.launcher3.taskbar.bubbles.BubbleBarController;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.SplitConfigurationOptions;
-import com.android.quickstep.OverviewCommandHelper;
-import com.android.quickstep.OverviewCommandHelper.CommandType;
 import com.android.quickstep.util.GroupTask;
-import com.android.quickstep.util.TISBindHelper;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskContainer;
 import com.android.quickstep.views.TaskView;
@@ -389,26 +386,13 @@
     /** Adjusts the hotseat for the bubble bar. */
     public void adjustHotseatForBubbleBar(boolean isBubbleBarVisible) {}
 
-    @Nullable
-    protected TISBindHelper getTISBindHelper() {
-        return null;
-    }
-
     /**
      * Launches the focused task in the Keyboard Quick Switch view through the OverviewCommandHelper
      * <p>
      * Use this helper method when the focused task may be the overview task.
      */
     public void launchKeyboardFocusedTask() {
-        TISBindHelper tisBindHelper = getTISBindHelper();
-        if (tisBindHelper == null) {
-            return;
-        }
-        OverviewCommandHelper overviewCommandHelper = tisBindHelper.getOverviewCommandHelper();
-        if (overviewCommandHelper == null) {
-            return;
-        }
-        overviewCommandHelper.addCommand(CommandType.HIDE);
+        mControllers.navButtonController.hideOverview();
     }
 
     /**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index 130b9b7..741853e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -34,6 +34,7 @@
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
+import android.util.ArraySet;
 import android.util.AttributeSet;
 import android.view.DisplayCutout;
 import android.view.InputDevice;
@@ -74,8 +75,10 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 import java.util.function.Predicate;
 
 /**
@@ -134,6 +137,8 @@
 
     private final int mNumStaticViews;
 
+    private Set<GroupTask> mPrevRecentTasks = Collections.emptySet();
+
     public TaskbarView(@NonNull Context context) {
         this(context, null);
     }
@@ -458,7 +463,7 @@
 
         // Skip static views and potential All Apps divider, if they are on the left.
         mNextViewIndex = mIsRtl ? 0 : mNumStaticViews;
-        if (getChildAt(mNextViewIndex) == mTaskbarDividerContainer) {
+        if (getChildAt(mNextViewIndex) == mTaskbarDividerContainer && !mAddedDividerForRecents) {
             mNextViewIndex++;
         }
 
@@ -613,7 +618,7 @@
         // accounted for when comparing current icon count to max number of icons.
         int nonTaskIconsToBeAdded = 1;
 
-        boolean supportsOverflow = Flags.taskbarOverflow();
+        boolean supportsOverflow = Flags.taskbarOverflow() && recentTasks.size() > 1;
         int overflowSize = 0;
         if (supportsOverflow) {
             mIdealNumIcons = mNextViewIndex + recentTasks.size() + nonTaskIconsToBeAdded;
@@ -636,6 +641,7 @@
         }
 
         // Add Recent/Running icons.
+        final Set<GroupTask> recentTasksSet = new ArraySet<>(recentTasks);
         for (GroupTask task : recentTasks) {
             if (mTaskbarOverflowView != null && overflownTasks != null
                     && overflownTasks.size() < itemsToAddToOverflow) {
@@ -664,12 +670,18 @@
             }
 
             View recentIcon = null;
-            while (isNextViewInSection(GroupTask.class)) {
+            // If a task is new, we should not reuse a view so that it animates in when it is added.
+            final boolean canReuseView = !taskbarRecentsLayoutTransition()
+                    || mPrevRecentTasks.contains(task);
+            while (canReuseView && isNextViewInSection(GroupTask.class)) {
                 recentIcon = getChildAt(mNextViewIndex);
 
                 // see if the view can be reused
                 if ((recentIcon.getSourceLayoutResId() != expectedLayoutResId)
-                        || (isCollection && (recentIcon.getTag() != task))) {
+                        || (isCollection && (recentIcon.getTag() != task))
+                        // Remove view corresponding to removed task so that it animates out.
+                        || (taskbarRecentsLayoutTransition()
+                                && !recentTasksSet.contains(recentIcon.getTag()))) {
                     removeAndRecycle(recentIcon);
                     recentIcon = null;
                 } else {
@@ -699,6 +711,8 @@
         while (isNextViewInSection(GroupTask.class)) {
             removeAndRecycle(getChildAt(mNextViewIndex));
         }
+
+        mPrevRecentTasks = recentTasksSet;
     }
 
     private boolean isNextViewInSection(Class<?> tagClass) {
@@ -758,7 +772,7 @@
      * aligned - returns 0.
      */
     public float getTranslationXForBubbleBarPosition(BubbleBarLocation location) {
-        if (!mControllerCallbacks.isBubbleBarEnabledInPersistentTaskbar()
+        if (!mControllerCallbacks.isBubbleBarEnabled()
                 || location == mBubbleBarLocation
                 || !mActivityContext.shouldStartAlignTaskbar()
         ) {
@@ -778,7 +792,7 @@
         int iconEnd = centerAlignIconEnd;
         if (mShouldTryStartAlign) {
             int startSpacingPx = deviceProfile.inlineNavButtonsEndSpacingPx;
-            if (mControllerCallbacks.isBubbleBarEnabledInPersistentTaskbar()
+            if (mControllerCallbacks.isBubbleBarEnabled()
                     && mBubbleBarLocation != null
                     && mActivityContext.shouldStartAlignTaskbar()) {
                 iconEnd = (int) getTaskBarIconsEndForBubbleBarLocation(mBubbleBarLocation);
@@ -839,6 +853,8 @@
             iconEnd += mAllAppsButtonTranslationOffset;
         }
 
+        mControllerCallbacks.onPreLayoutChildren();
+
         int count = getChildCount();
         for (int i = count; i > 0; i--) {
             View child = getChildAt(i - 1);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java
index f2bd4d0..c7ef960 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.taskbar;
 
+import static com.android.launcher3.Flags.taskbarRecentsLayoutTransition;
+import static com.android.launcher3.config.FeatureFlags.enableTaskbarPinning;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_ALLAPPS_BUTTON_LONG_PRESS;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_ALLAPPS_BUTTON_TAP;
 import static com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_TASKBAR_OVERFLOW;
@@ -23,6 +25,7 @@
 import android.annotation.SuppressLint;
 import android.content.Context;
 import android.view.GestureDetector;
+import android.view.HapticFeedbackConstants;
 import android.view.InputDevice;
 import android.view.MotionEvent;
 import android.view.View;
@@ -34,7 +37,6 @@
 import com.android.launcher3.taskbar.bubbles.BubbleBarViewController;
 import com.android.launcher3.util.DisplayController;
 import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
-import com.android.wm.shell.Flags;
 import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
 
 /**
@@ -110,6 +112,13 @@
         return new TaskbarHoverToolTipController(mActivity, mTaskbarView, icon);
     }
 
+    /** Callback invoked before Taskbar icons are laid out. */
+    void onPreLayoutChildren() {
+        if (enableTaskbarPinning() && taskbarRecentsLayoutTransition()) {
+            mControllers.taskbarViewController.updateTaskbarIconTranslationXForPinning();
+        }
+    }
+
     /**
      * Notifies launcher to update icon alignment.
      */
@@ -150,10 +159,9 @@
                 .orElse(0f);
     }
 
-    /** Returns true if bubble bar controllers present and enabled in persistent taskbar. */
-    public boolean isBubbleBarEnabledInPersistentTaskbar() {
-        return Flags.enableBubbleBarInPersistentTaskBar()
-                && mControllers.bubbleControllers.isPresent();
+    /** Returns true if bubble bar controllers are present. */
+    public boolean isBubbleBarEnabled() {
+        return mControllers.bubbleControllers.isPresent();
     }
 
     /** Returns on click listener for the taskbar overflow view. */
@@ -223,15 +231,19 @@
 
         @Override
         public void onLongPress(@NonNull MotionEvent event) {
-            maybeShowPinningView(event);
+            if (maybeShowPinningView(event)) {
+                mTaskbarView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+            }
         }
 
-        private void maybeShowPinningView(@NonNull MotionEvent event) {
+        /** Returns true if the taskbar pinning popup view was shown for {@code event}. */
+        private boolean maybeShowPinningView(@NonNull MotionEvent event) {
             if (!DisplayController.isPinnedTaskbar(mActivity) || mTaskbarView.isEventOverAnyItem(
                     event)) {
-                return;
+                return false;
             }
             mControllers.taskbarPinningController.showPinningView(mTaskbarView, event.getRawX());
+            return true;
         }
     }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 4acf2fe..438478f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -15,10 +15,17 @@
  */
 package com.android.launcher3.taskbar;
 
+import static android.animation.LayoutTransition.APPEARING;
+import static android.animation.LayoutTransition.CHANGE_APPEARING;
+import static android.animation.LayoutTransition.CHANGE_DISAPPEARING;
+import static android.animation.LayoutTransition.DISAPPEARING;
+
+import static com.android.app.animation.Interpolators.EMPHASIZED;
 import static com.android.app.animation.Interpolators.FINAL_FRAME;
 import static com.android.app.animation.Interpolators.LINEAR;
 import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
 import static com.android.launcher3.Flags.taskbarOverflow;
+import static com.android.launcher3.Flags.taskbarRecentsLayoutTransition;
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
 import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
@@ -40,13 +47,17 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorSet;
+import android.animation.LayoutTransition;
+import android.animation.LayoutTransition.TransitionListener;
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.annotation.NonNull;
 import android.graphics.Rect;
+import android.util.FloatProperty;
 import android.util.Log;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewGroup;
 import android.view.animation.Interpolator;
 
 import androidx.annotation.Nullable;
@@ -74,11 +85,11 @@
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.LauncherBindableItemsContainer;
 import com.android.launcher3.util.MultiPropertyFactory;
+import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
 import com.android.launcher3.util.MultiTranslateDelegate;
 import com.android.launcher3.util.MultiValueAlpha;
 import com.android.quickstep.util.GroupTask;
 import com.android.systemui.shared.recents.model.Task;
-import com.android.wm.shell.Flags;
 import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
 
 import java.io.PrintWriter;
@@ -114,6 +125,11 @@
     /** Used if an unexpected edge case is hit in {@link #getPositionInHotseat}. */
     private static final float ERROR_POSITION_IN_HOTSEAT_NOT_FOUND = -100;
 
+    private static final int TRANSITION_DELAY = 50;
+    private static final int TRANSITION_DEFAULT_DURATION = 500;
+    private static final int TRANSITION_FADE_IN_DURATION = 167;
+    private static final int TRANSITION_FADE_OUT_DURATION = 83;
+
     private static boolean sEnableModelLoadingForTests = true;
 
     private final TaskbarActivityContext mActivity;
@@ -158,7 +174,9 @@
 
     private final View.OnLayoutChangeListener mTaskbarViewLayoutChangeListener =
             (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
-                updateTaskbarIconTranslationXForPinning();
+                if (!taskbarRecentsLayoutTransition()) {
+                    updateTaskbarIconTranslationXForPinning();
+                }
                 if (BubbleBarController.isBubbleBarEnabled()) {
                     mControllers.navbarButtonsViewController.onLayoutsUpdated();
                 }
@@ -284,8 +302,7 @@
 
     /** Returns whether taskbar should be moved on the bubble bar location update. */
     private boolean shouldMoveTaskbarOnBubbleBarLocationUpdate() {
-        return Flags.enableBubbleBarInPersistentTaskBar()
-                && mControllers.bubbleControllers.isPresent()
+        return mControllers.bubbleControllers.isPresent()
                 && mActivity.shouldStartAlignTaskbar()
                 && mActivity.isThreeButtonNav();
     }
@@ -432,7 +449,7 @@
         }
     }
 
-    private void updateTaskbarIconTranslationXForPinning() {
+    void updateTaskbarIconTranslationXForPinning() {
         View[] iconViews = mTaskbarView.getIconViews();
         float scale = mTaskbarIconTranslationXForPinning.value;
         float transientTaskbarAllAppsOffset = mActivity.getResources().getDimension(
@@ -1076,6 +1093,89 @@
     /** Called when there's a change in running apps to update the UI. */
     public void commitRunningAppsToUI() {
         mModelCallbacks.commitRunningAppsToUI();
+        if (taskbarRecentsLayoutTransition() && mTaskbarView.getLayoutTransition() == null) {
+            // Set up after the first commit so that the initial recents do not animate (janky).
+            mTaskbarView.setLayoutTransition(createLayoutTransitionForRunningApps());
+        }
+    }
+
+    private LayoutTransition createLayoutTransitionForRunningApps() {
+        LayoutTransition layoutTransition = new LayoutTransition();
+        layoutTransition.setDuration(TRANSITION_DEFAULT_DURATION);
+        layoutTransition.addTransitionListener(new TransitionListener() {
+
+            @Override
+            public void startTransition(
+                    LayoutTransition transition, ViewGroup container, View view, int type) {
+                if (type == APPEARING) {
+                    view.setAlpha(0f);
+                    view.setScaleX(0f);
+                    view.setScaleY(0f);
+                }
+            }
+
+            @Override
+            public void endTransition(
+                    LayoutTransition transition, ViewGroup container, View view, int type) {
+                // Do nothing.
+            }
+        });
+
+        // Appearing.
+        AnimatorSet appearingSet = new AnimatorSet();
+        Animator appearingAlphaAnimator = ObjectAnimator.ofFloat(null, "alpha", 0f, 1f);
+        appearingAlphaAnimator.setInterpolator(Interpolators.clampToProgress(LINEAR, 0f,
+                (float) TRANSITION_FADE_IN_DURATION / TRANSITION_DEFAULT_DURATION));
+        Animator appearingScaleAnimator = ObjectAnimator.ofFloat(null, SCALE_PROPERTY, 0f, 1f);
+        appearingScaleAnimator.setInterpolator(EMPHASIZED);
+        appearingSet.playTogether(appearingAlphaAnimator, appearingScaleAnimator);
+        layoutTransition.setAnimator(APPEARING, appearingSet);
+        layoutTransition.setStartDelay(APPEARING, TRANSITION_DELAY);
+
+        // Disappearing.
+        AnimatorSet disappearingSet = new AnimatorSet();
+        Animator disappearingAlphaAnimator = ObjectAnimator.ofFloat(null, "alpha", 1f, 0f);
+        disappearingAlphaAnimator.setInterpolator(Interpolators.clampToProgress(LINEAR,
+                (float) TRANSITION_DELAY / TRANSITION_DEFAULT_DURATION,
+                (float) (TRANSITION_DELAY + TRANSITION_FADE_OUT_DURATION)
+                        / TRANSITION_DEFAULT_DURATION));
+        Animator disappearingScaleAnimator = ObjectAnimator.ofFloat(null, SCALE_PROPERTY, 1f, 0f);
+        disappearingScaleAnimator.setInterpolator(EMPHASIZED);
+        disappearingSet.playTogether(disappearingAlphaAnimator, disappearingScaleAnimator);
+        layoutTransition.setAnimator(DISAPPEARING, disappearingSet);
+
+        // Change transitions.
+        FloatProperty<View> translateXPinning = new FloatProperty<>("translateXPinning") {
+            @Override
+            public void setValue(View view, float value) {
+                getTranslationXForPinning(view).setValue(value);
+            }
+
+            @Override
+            public Float get(View view) {
+                return getTranslationXForPinning(view).getValue();
+            }
+
+            private MultiProperty getTranslationXForPinning(View view) {
+                return ((Reorderable) view).getTranslateDelegate()
+                        .getTranslationX(INDEX_TASKBAR_PINNING_ANIM);
+            }
+        };
+        AnimatorSet changeSet = new AnimatorSet();
+        changeSet.playTogether(
+                layoutTransition.getAnimator(CHANGE_APPEARING),
+                ObjectAnimator.ofFloat(null, translateXPinning, 0f, 1f));
+
+        // Change appearing.
+        layoutTransition.setAnimator(CHANGE_APPEARING, changeSet);
+        layoutTransition.setInterpolator(CHANGE_APPEARING, EMPHASIZED);
+
+        // Change disappearing.
+        layoutTransition.setAnimator(CHANGE_DISAPPEARING, changeSet);
+        layoutTransition.setInterpolator(CHANGE_DISAPPEARING, EMPHASIZED);
+        layoutTransition.setStartDelay(CHANGE_DISAPPEARING, TRANSITION_DELAY);
+
+        return layoutTransition;
     }
 
     /**
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
index 07d86e4..ddbf3b7 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
@@ -141,6 +141,7 @@
         if (isOpen()) {
             mSlideInView.close(true);
         } else {
+            mControllers.taskbarPopupController.maybeCloseMultiInstanceMenu();
             show(true, showKeyboard);
         }
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
index 5b3c233..9683f8b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
@@ -107,6 +107,7 @@
     private final Context mContext;
     private final BubbleBarView mBarView;
     private final ArrayMap<String, BubbleBarBubble> mBubbles = new ArrayMap<>();
+    private final ArrayMap<String, BubbleBarBubble> mSuppressedBubbles = new ArrayMap<>();
 
     private static final Executor BUBBLE_STATE_EXECUTOR = Executors.newSingleThreadExecutor(
             new SimpleThreadFactory("BubbleStateUpdates-", THREAD_PRIORITY_BACKGROUND));
@@ -188,6 +189,10 @@
             }
         });
         mSharedState.bubbleInfoItems = Arrays.asList(bubbleInfoItems);
+        mSharedState.suppressedBubbleInfoItems = new ArrayList<>(mSuppressedBubbles.size());
+        for (int i = 0; i < mSuppressedBubbles.size(); i++) {
+            mSharedState.suppressedBubbleInfoItems.add(mSuppressedBubbles.valueAt(i).getInfo());
+        }
     }
 
     /** Initializes controllers. */
@@ -290,7 +295,11 @@
         if (sharedState.bubbleBarLocation != null) {
             updateBubbleBarLocationInternal(sharedState.bubbleBarLocation);
         }
-        List<BubbleInfo> bubbleInfos = sharedState.bubbleInfoItems;
+        restoreSavedBubbles(sharedState.bubbleInfoItems);
+        restoreSuppressed(sharedState.suppressedBubbleInfoItems);
+    }
+
+    private void restoreSavedBubbles(List<BubbleInfo> bubbleInfos) {
         if (bubbleInfos == null || bubbleInfos.isEmpty()) return;
         // Iterate in reverse because new bubbles are added in front and the list is in order.
         for (int i = bubbleInfos.size() - 1; i >= 0; i--) {
@@ -304,6 +313,18 @@
         }
     }
 
+    private void restoreSuppressed(List<BubbleInfo> bubbleInfos) {
+        if (bubbleInfos == null || bubbleInfos.isEmpty()) return;
+        for (BubbleInfo bubbleInfo : bubbleInfos.reversed()) {
+            BubbleBarBubble bb = mBubbleCreator.populateBubble(mContext, bubbleInfo,
+                    mBarView, /* existingBubble= */
+                    null);
+            if (bb != null) {
+                mSuppressedBubbles.put(bb.getKey(), bb);
+            }
+        }
+    }
+
     private void applyViewChanges(BubbleBarViewUpdate update) {
         final boolean isCollapsed = (update.expandedChanged && !update.expanded)
                 || (!update.expandedChanged && !mBubbleBarViewController.isExpanded());
@@ -375,9 +396,7 @@
 
         // if a bubble was updated upstream, but removed before the update was received, add it back
         if (update.updatedBubble != null && !mBubbles.containsKey(update.updatedBubble.getKey())) {
-            mBubbles.put(update.updatedBubble.getKey(), update.updatedBubble);
-            mBubbleBarViewController.addBubble(
-                    update.updatedBubble, isExpanding, suppressAnimation);
+            addBubbleInternally(update.updatedBubble, isExpanding, suppressAnimation);
         }
 
         if (update.addedBubble != null && isCollapsed) {
@@ -405,10 +424,29 @@
             mBubbleBarViewController.showOverflow(true);
         }
 
-        // Update the visibility if this is the initial state or if there are no bubbles.
+        if (update.suppressedBubbleKey != null) {
+            BubbleBarBubble bb = mBubbles.remove(update.suppressedBubbleKey);
+            if (bb != null) {
+                mSuppressedBubbles.put(update.suppressedBubbleKey, bb);
+                mBubbleBarViewController.removeBubble(bb);
+            }
+        }
+        if (update.unsuppressedBubbleKey != null) {
+            BubbleBarBubble bb = mSuppressedBubbles.remove(update.unsuppressedBubbleKey);
+            if (bb != null) {
+                // Unsuppressing an existing bubble should not cause the bar to expand or animate
+                addBubbleInternally(bb, /* isExpanding= */ false, /* suppressAnimation= */ true);
+                if (mBubbleBarViewController.isHiddenForNoBubbles()) {
+                    mBubbleBarViewController.setHiddenForBubbles(false);
+                }
+            }
+        }
+
+        // Update the visibility if this is the initial state, if there are no bubbles, or if the
+        // animation is suppressed.
         // If this is the initial bubble, the bubble bar will become visible as part of the
         // animation.
-        if (update.initialState || mBubbles.isEmpty()) {
+        if (update.initialState || mBubbles.isEmpty() || suppressAnimation) {
             mBubbleBarViewController.setHiddenForBubbles(mBubbles.isEmpty());
         }
         mBubbleStashedHandleViewController.ifPresent(
@@ -439,12 +477,6 @@
                 mBubbleBarViewController.reorderBubbles(newOrder);
             }
         }
-        if (update.suppressedBubbleKey != null) {
-            // TODO: (b/273316505) handle suppression
-        }
-        if (update.unsuppressedBubbleKey != null) {
-            // TODO: (b/273316505) handle suppression
-        }
         if (update.selectedBubbleKey != null) {
             if (mSelectedBubble == null
                     || !update.selectedBubbleKey.equals(mSelectedBubble.getKey())) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
index dd1b0ca..d00959e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
@@ -183,6 +183,11 @@
         if (!Flags.enableOptionalBubbleOverflow()) {
             showOverflow(true);
         }
+        if (!mBubbleStashController.isTransientTaskBar()) {
+            // TODO(b/380274085) for transient taskbar mode, the click is also handled by the input
+            //  consumer. This check can be removed once b/380274085 is fixed.
+            mBarView.setOnClickListener(v -> setExpanded(!mBarView.isExpanded()));
+        }
         mBarView.addOnLayoutChangeListener(
                 (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
                     mTaskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged();
@@ -650,10 +655,12 @@
     }
 
     private void updateVisibilityForStateChange() {
-        if (!mHiddenForSysui && !mHiddenForNoBubbles && !mHiddenForStashed) {
-            mBarView.setVisibility(VISIBLE);
-        } else {
+        boolean hiddenForStashedAndNotAnimating =
+                mHiddenForStashed && !mBubbleBarViewAnimator.isAnimating();
+        if (mHiddenForSysui || mHiddenForNoBubbles || hiddenForStashedAndNotAnimating) {
             mBarView.setVisibility(INVISIBLE);
+        } else {
+            mBarView.setVisibility(VISIBLE);
         }
     }
 
@@ -1215,6 +1222,7 @@
         pw.println("Bubble bar view controller state:");
         pw.println("  mHiddenForSysui: " + mHiddenForSysui);
         pw.println("  mHiddenForNoBubbles: " + mHiddenForNoBubbles);
+        pw.println("  mHiddenForStashed: " + mHiddenForStashed);
         pw.println("  mShouldShowEducation: " + mShouldShowEducation);
         pw.println("  mBubbleBarTranslationY.value: " + mBubbleBarTranslationY.value);
         pw.println("  mBubbleBarSwipeUpTranslationY: " + mBubbleBarSwipeUpTranslationY);
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt
index df00696..3e3f569 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt
@@ -224,12 +224,13 @@
 
     override fun isBubbleBarVisible(): Boolean = bubbleBarViewController.hasBubbles() && !isStashed
 
-    override fun onNewBubbleAnimationInterrupted(isStashed: Boolean, bubbleBarTranslationY: Float) =
+    override fun onNewBubbleAnimationInterrupted(isStashed: Boolean, bubbleBarTranslationY: Float) {
         if (isStashed) {
             stashBubbleBarImmediate()
         } else {
             showBubbleBarImmediate(bubbleBarTranslationY)
         }
+    }
 
     /** Check if [ev] belongs to the stash handle or the bubble bar views. */
     override fun isEventOverBubbleBarViews(ev: MotionEvent): Boolean {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 5cb6e86..4c5e655 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -48,6 +48,7 @@
 import static com.android.launcher3.popup.SystemShortcut.BUBBLE_SHORTCUT;
 import static com.android.launcher3.popup.SystemShortcut.DONT_SUGGEST_APP;
 import static com.android.launcher3.popup.SystemShortcut.INSTALL;
+import static com.android.launcher3.popup.SystemShortcut.PIN_UNPIN_ITEM;
 import static com.android.launcher3.popup.SystemShortcut.PRIVATE_PROFILE_INSTALL;
 import static com.android.launcher3.popup.SystemShortcut.UNINSTALL_APP;
 import static com.android.launcher3.popup.SystemShortcut.WIDGETS;
@@ -65,7 +66,6 @@
 import static com.android.quickstep.util.SplitAnimationTimings.TABLET_HOME_TO_SPLIT;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY;
 import static com.android.wm.shell.Flags.enableBubbleAnything;
-import static com.android.wm.shell.Flags.enableBubbleBarInPersistentTaskBar;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;
 
 import android.animation.Animator;
@@ -458,6 +458,10 @@
         // Order matters as it affects order of appearance in popup container
         List<SystemShortcut.Factory> shortcuts = new ArrayList(Arrays.asList(
                 APP_INFO, WellbeingModel.SHORTCUT_FACTORY, mHotseatPredictionController));
+
+        if (Flags.enablePinningAppWithContextMenu()) {
+            shortcuts.add(0, PIN_UNPIN_ITEM);
+        }
         shortcuts.addAll(getSplitShortcuts());
         shortcuts.add(WIDGETS);
         shortcuts.add(INSTALL);
@@ -601,7 +605,7 @@
             case QUICK_SWITCH_STATE_ORDINAL: {
                 RecentsView rv = getOverviewPanel();
                 TaskView currentPageTask = rv.getCurrentPageTaskView();
-                TaskView fallbackTask = rv.getTaskViewAt(0);
+                TaskView fallbackTask = rv.getFirstTaskView();
                 if (currentPageTask != null || fallbackTask != null) {
                     TaskView taskToLaunch = currentPageTask;
                     if (currentPageTask == null) {
@@ -1134,9 +1138,7 @@
     /** Provides the translation X for the hotseat item. */
     public int getHotseatItemTranslationX(ItemInfo itemInfo) {
         int translationX = 0;
-        if (isBubbleBarEnabled()
-                && enableBubbleBarInPersistentTaskBar()
-                && mBubbleBarLocation != null) {
+        if (isBubbleBarEnabled() && mBubbleBarLocation != null) {
             boolean isBubblesOnLeft = mBubbleBarLocation.isOnLeft(isRtl(getResources()));
             translationX += mDeviceProfile
                     .getHotseatTranslationXForNavBar(this, isBubblesOnLeft);
@@ -1432,12 +1434,6 @@
         return (mTaskbarUIController != null && mTaskbarUIController.hasBubbles());
     }
 
-    @NonNull
-    @Override
-    public TISBindHelper getTISBindHelper() {
-        return mTISBindHelper;
-    }
-
     @Override
     public boolean handleIncorrectSplitTargetSelection() {
         if (!enableSplitContextually() || !mSplitSelectStateController.isSplitSelectActive()) {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/SystemApiWrapper.kt b/quickstep/src/com/android/launcher3/uioverrides/SystemApiWrapper.kt
index 374db6a..2e2d7cc 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/SystemApiWrapper.kt
+++ b/quickstep/src/com/android/launcher3/uioverrides/SystemApiWrapper.kt
@@ -63,7 +63,7 @@
 
     override fun createFadeOutAnimOptions(): ActivityOptions =
         ActivityOptions.makeBasic().apply {
-            remoteTransition = RemoteTransition(FadeOutRemoteTransition())
+            remoteTransition = RemoteTransition(FadeOutRemoteTransition(), "FadeOut")
         }
 
     override fun queryAllUsers(): Map<UserHandle, UserIconInfo> {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
index d387794..dae63af 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
@@ -102,7 +102,7 @@
 
     @Override
     public int getTitle() {
-        return R.string.all_apps_label;
+        return R.string.all_apps_list_label;
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
index 8ad00bf..1907b4e 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
@@ -122,7 +122,7 @@
             config.setInterpolator(ANIM_WORKSPACE_FADE, ACCELERATE);
 
             if (DisplayController.getNavigationMode(mContainer).hasGestures
-                    && overview.getTaskViewCount() > 0) {
+                    && overview.hasTaskViews()) {
                 // Overview is going offscreen, so keep it at its current scale and opacity.
                 config.setInterpolator(ANIM_OVERVIEW_SCALE, FINAL_FRAME);
                 config.setInterpolator(ANIM_OVERVIEW_FADE, FINAL_FRAME);
@@ -178,7 +178,7 @@
                 config.setInterpolator(ANIM_WORKSPACE_TRANSLATE, ACCELERATE);
 
                 // Scrolling in tasks, so show straight away
-                if (overview.getTaskViewCount() > 0) {
+                if (overview.hasTaskViews()) {
                     config.setInterpolator(ANIM_OVERVIEW_FADE, INSTANT);
                 } else {
                     config.setInterpolator(ANIM_OVERVIEW_FADE, OVERSHOOT_1_2);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index 9164405..49a5ac5 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -223,7 +223,7 @@
         updateNonOverviewAnim(QUICK_SWITCH_FROM_HOME, nonOverviewBuilder);
         mNonOverviewAnim.dispatchOnStart();
 
-        if (mRecentsView.getTaskViewCount() == 0) {
+        if (!mRecentsView.hasTaskViews()) {
             mRecentsView.setOnEmptyMessageUpdatedListener(isEmpty -> {
                 if (!isEmpty && mSwipeDetector.isDraggingState()) {
                     // We have loaded tasks, update the animators to start at the correct scale etc.
@@ -256,7 +256,7 @@
         mRecentsView.setFullscreenProgress(fromState.getOverviewFullscreenProgress());
         mLauncher.getActionsView().getVisibilityAlpha().updateValue(
                 (fromState.getVisibleElements(mLauncher) & OVERVIEW_ACTIONS) != 0 ? 1f : 0f);
-        mRecentsView.setTaskIconScaledDown(true);
+        mRecentsView.setTaskIconVisible(false);
 
         float[] scaleAndOffset = toState.getOverviewScaleAndOffset(mLauncher);
         // As we drag right, animate the following properties:
@@ -269,7 +269,7 @@
         // since we need to take potential taskbar into account.
         xAnim.setViewBackgroundColor(mLauncher.getScrimView(),
                 QUICK_SWITCH_FROM_HOME.getWorkspaceScrimColor(mLauncher), LINEAR);
-        if (mRecentsView.getTaskViewCount() == 0) {
+        if (!mRecentsView.hasTaskViews()) {
             xAnim.addFloat(mRecentsView, CONTENT_ALPHA, 0f, 1f, LINEAR);
         }
         mXOverviewAnim = xAnim.createPlaybackController();
@@ -340,7 +340,7 @@
                 public void onAnimationEnd(Animator animation) {
                     onAnimationToStateCompleted(OVERVIEW);
                     // Animate the icon after onAnimationToStateCompleted() so it doesn't clobber.
-                    mRecentsView.animateUpTaskIconScale();
+                    mRecentsView.startIconFadeInOnGestureComplete();
                 }
             });
             overviewAnim.start();
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java
index 4df0f63..68940dc 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java
@@ -48,9 +48,9 @@
      * @return true if we should intercept the motion event
      */
     boolean canInterceptTouch(MotionEvent ev) {
-        if (mRecentsView.getTaskViewCount() > 0) {
+        if (mRecentsView.hasTaskViews()) {
             // Allow swiping up in the gap between the hotseat and overview.
-            return ev.getY() >= mRecentsView.getTaskViewAt(0).getBottom();
+            return ev.getY() >= mRecentsView.getFirstTaskView().getBottom();
         } else {
             // If there are no tasks, we only intercept if we're below the hotseat height.
             return isTouchOverHotseat(mLauncher, ev);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
index 31e4e33..d673720 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
@@ -149,7 +149,7 @@
         mOverviewPanel.setFullscreenProgress(progress);
         if (progress > UPDATE_SYSUI_FLAGS_THRESHOLD) {
             int sysuiFlags = 0;
-            TaskView tv = mOverviewPanel.getTaskViewAt(0);
+            TaskView tv = mOverviewPanel.getFirstTaskView();
             if (tv != null) {
                 sysuiFlags = tv.getTaskContainers().getFirst().getSysUiStatusNavFlags();
             }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
index 202276e..d622987 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
@@ -66,7 +66,7 @@
 
     protected final CONTAINER mContainer;
     private final SingleAxisSwipeDetector mDetector;
-    private final RecentsView mRecentsView;
+    private final RecentsView<?, ?> mRecentsView;
     private final Rect mTempRect = new Rect();
     private final boolean mIsRtl;
 
@@ -157,17 +157,15 @@
             } else {
                 mTaskBeingDragged = null;
 
-                for (int i = 0; i < mRecentsView.getTaskViewCount(); i++) {
-                    TaskView view = mRecentsView.getTaskViewAt(i);
-
-                    if (mRecentsView.isTaskViewVisible(view) && mContainer.getDragLayer()
-                            .isEventOverView(view, ev)) {
+                for (TaskView taskView : mRecentsView.getTaskViews()) {
+                    if (mRecentsView.isTaskViewVisible(taskView) && mContainer.getDragLayer()
+                            .isEventOverView(taskView, ev)) {
                         // Disable swiping up and down if the task overlay is modal.
                         if (isRecentsModal()) {
                             mTaskBeingDragged = null;
                             break;
                         }
-                        mTaskBeingDragged = view;
+                        mTaskBeingDragged = taskView;
                         int upDirection = mRecentsView.getPagedOrientationHandler()
                                 .getUpDirection(mIsRtl);
 
@@ -179,10 +177,10 @@
                         // - We support gestures to enter overview
                         // - It's the focused task if in grid view
                         // - The task is snapped
-                        mAllowGoingDown = i == mRecentsView.getCurrentPage()
+                        mAllowGoingDown = taskView == mRecentsView.getCurrentPageTaskView()
                                 && DisplayController.getNavigationMode(mContainer).hasGestures
                                 && (!mRecentsView.showAsGrid() || mTaskBeingDragged.isLargeTile())
-                                && mRecentsView.isTaskInExpectedScrollPosition(i);
+                                && mRecentsView.isTaskInExpectedScrollPosition(taskView);
 
                         directionsToDetectScroll = mAllowGoingDown ? DIRECTION_BOTH : upDirection;
                         break;
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index fbc8d6a..03394ef 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -33,11 +33,14 @@
 import static com.android.launcher3.Flags.enableAdditionalHomeAnimations;
 import static com.android.launcher3.Flags.enableGridOnlyOverview;
 import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
+import static com.android.launcher3.Flags.msdlFeedback;
 import static com.android.launcher3.PagedView.INVALID_PAGE;
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.IGNORE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_GESTURE;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_ENTER_DESKTOP_MODE;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_EXIT_DESKTOP_MODE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_LEFT;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_RIGHT;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
@@ -121,13 +124,13 @@
 import com.android.launcher3.taskbar.TaskbarUIController;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
 import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.MSDLPlayerWrapper;
 import com.android.launcher3.util.SafeCloseable;
 import com.android.launcher3.util.TraceHelper;
 import com.android.launcher3.util.VibratorWrapper;
 import com.android.launcher3.util.WindowBounds;
 import com.android.quickstep.GestureState.GestureEndTarget;
 import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle;
-import com.android.quickstep.fallback.window.RecentsWindowManager;
 import com.android.quickstep.util.ActiveGestureErrorDetector;
 import com.android.quickstep.util.ActiveGestureLog;
 import com.android.quickstep.util.ActiveGestureProtoLogProxy;
@@ -149,6 +152,7 @@
 import com.android.quickstep.views.RecentsViewContainer;
 import com.android.quickstep.views.TaskContainer;
 import com.android.quickstep.views.TaskView;
+import com.android.quickstep.views.TaskViewType;
 import com.android.systemui.animation.TransitionAnimator;
 import com.android.systemui.contextualeducation.GestureType;
 import com.android.systemui.shared.recents.model.Task;
@@ -164,6 +168,8 @@
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.shared.startingsurface.SplashScreenExitAnimationUtils;
 
+import com.google.android.msdl.data.model.MSDLToken;
+
 import kotlin.Unit;
 
 import java.util.ArrayList;
@@ -206,6 +212,8 @@
     protected MultiStateCallback mStateCallback;
     protected boolean mCanceled;
     private boolean mRecentsViewScrollLinked = false;
+    // The previous task view type before the user quick switches between tasks
+    private TaskViewType mPreviousTaskViewType;
 
     private final Runnable mLauncherOnDestroyCallback = () -> {
         ActiveGestureProtoLogProxy.logLauncherDestroyed();
@@ -301,7 +309,6 @@
     private static final int LOG_NO_OP_PAGE_INDEX = -1;
 
     protected final TaskAnimationManager mTaskAnimationManager;
-    protected final RecentsWindowManager mRecentsWindowManager;
     // Either RectFSpringAnim (if animating home) or ObjectAnimator (from mCurrentShift) otherwise
     private RunningWindowAnim[] mRunningWindowAnim;
     // Possible second animation running at the same time as mRunningWindowAnim
@@ -362,10 +369,13 @@
     @Nullable
     private RemoteAnimationTargets.ReleaseCheck mSwipePipToHomeReleaseCheck = null;
 
+    private final MSDLPlayerWrapper mMSDLPlayerWrapper;
+
     public AbsSwipeUpHandler(Context context, RecentsAnimationDeviceState deviceState,
             TaskAnimationManager taskAnimationManager, GestureState gestureState,
             long touchTimeMs, boolean continuingLastGesture,
-            InputConsumerController inputConsumer, RecentsWindowManager recentsWindowManager) {
+            InputConsumerController inputConsumer,
+            MSDLPlayerWrapper msdlPlayerWrapper) {
         super(context, deviceState, gestureState);
         mContainerInterface = gestureState.getContainerInterface();
         mContextInitListener =
@@ -381,7 +391,6 @@
                     endLauncherTransitionController();
                 }, new InputProxyHandlerFactory(mContainerInterface, mGestureState));
         mTaskAnimationManager = taskAnimationManager;
-        mRecentsWindowManager = recentsWindowManager;
         mTouchTimeMs = touchTimeMs;
         mContinuingLastGesture = continuingLastGesture;
 
@@ -392,6 +401,8 @@
         mSplashMainWindowShiftLength = -res
                 .getDimensionPixelSize(R.dimen.starting_surface_exit_animation_window_shift_length);
 
+        mMSDLPlayerWrapper = msdlPlayerWrapper;
+
         initTransitionEndpoints(mRemoteTargetHandles[0].getTaskViewSimulator()
                 .getOrientationState().getLauncherDeviceProfile());
         initStateCallbacks();
@@ -695,6 +706,10 @@
             return;
         }
         mRecentsView.onGestureAnimationStart(runningTasks, mDeviceState.getRotationTouchHelper());
+        TaskView currentPageTaskView = mRecentsView.getCurrentPageTaskView();
+        if (currentPageTaskView != null) {
+            mPreviousTaskViewType = currentPageTaskView.getType();
+        }
     }
 
     private void launcherFrameDrawn() {
@@ -1478,21 +1493,29 @@
             return;
         }
 
-        StatsLogManager.EventEnum event;
+        ArrayList<StatsLogManager.EventEnum> events = new ArrayList<>();
         switch (endTarget) {
             case HOME:
-                event = LAUNCHER_HOME_GESTURE;
+                events.add(LAUNCHER_HOME_GESTURE);
                 break;
             case RECENTS:
-                event = LAUNCHER_OVERVIEW_GESTURE;
+                events.add(LAUNCHER_OVERVIEW_GESTURE);
                 break;
             case LAST_TASK:
             case NEW_TASK:
-                event = mLogDirectionUpOrLeft ? LAUNCHER_QUICKSWITCH_LEFT
-                        : LAUNCHER_QUICKSWITCH_RIGHT;
+                events.add(mLogDirectionUpOrLeft ? LAUNCHER_QUICKSWITCH_LEFT
+                        : LAUNCHER_QUICKSWITCH_RIGHT);
+                if (targetTask != null && DesktopModeStatus.canEnterDesktopMode(mContext)
+                        && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_QUICK_SWITCH.isTrue()) {
+                    if (targetTask.getType() == TaskViewType.DESKTOP) {
+                        events.add(LAUNCHER_QUICKSWITCH_ENTER_DESKTOP_MODE);
+                    } else if (mPreviousTaskViewType == TaskViewType.DESKTOP) {
+                        events.add(LAUNCHER_QUICKSWITCH_EXIT_DESKTOP_MODE);
+                    }
+                }
                 break;
             default:
-                event = IGNORE;
+                events.add(IGNORE);
         }
         StatsLogger logger = StatsLogManager.newInstance(
                         mContainer != null ? mContainer.asContext() : mContext).logger()
@@ -1509,7 +1532,7 @@
                 ? LOG_NO_OP_PAGE_INDEX
                 : mRecentsView.getNextPage();
         logger.withRank(pageIndex);
-        logger.log(event);
+        events.forEach(logger::log);
     }
 
     protected abstract HomeAnimationFactory createHomeAnimationFactory(
@@ -1995,6 +2018,7 @@
     @UiThread
     private void startNewTask() {
         TaskView taskToLaunch = mRecentsView == null ? null : mRecentsView.getNextPageTaskView();
+        doLogGesture(NEW_TASK, taskToLaunch);
         startNewTask(success -> {
             if (!success) {
                 reset();
@@ -2003,7 +2027,6 @@
                 endLauncherTransitionController();
                 updateSysUiFlags(1 /* windowProgress == overview */);
             }
-            doLogGesture(NEW_TASK, taskToLaunch);
         });
     }
 
@@ -2272,7 +2295,11 @@
     }
 
     protected void performHapticFeedback() {
-        VibratorWrapper.INSTANCE.get(mContext).vibrate(OVERVIEW_HAPTIC);
+        if (msdlFeedback()) {
+            mMSDLPlayerWrapper.playToken(MSDLToken.SWIPE_THRESHOLD_INDICATOR);
+        } else {
+            VibratorWrapper.INSTANCE.get(mContext).vibrate(OVERVIEW_HAPTIC);
+        }
     }
 
     public Consumer<MotionEvent> getRecentsViewDispatcher(float navbarRotation) {
diff --git a/quickstep/src/com/android/quickstep/DisplayModel.kt b/quickstep/src/com/android/quickstep/DisplayModel.kt
new file mode 100644
index 0000000..cbc2f7d
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/DisplayModel.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep
+
+import android.content.Context
+import android.hardware.display.DisplayManager
+import android.util.Log
+import android.util.SparseArray
+import androidx.core.util.valueIterator
+import com.android.quickstep.DisplayModel.DisplayResource
+
+/** data model for managing resources with lifecycles that match that of the connected display */
+abstract class DisplayModel<RESOURCE_TYPE : DisplayResource>(val context: Context) {
+
+    companion object {
+        private const val TAG = "DisplayViewModel"
+        private const val DEBUG = false
+    }
+
+    protected val displayManager =
+        context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
+    protected val displayResourceArray = SparseArray<RESOURCE_TYPE>()
+
+    abstract fun createDisplayResource(displayId: Int)
+
+    protected val displayListener: DisplayManager.DisplayListener =
+        (object : DisplayManager.DisplayListener {
+            override fun onDisplayAdded(displayId: Int) {
+                if (DEBUG) Log.d(TAG, "onDisplayAdded: displayId=$displayId")
+                createDisplayResource(displayId)
+            }
+
+            override fun onDisplayRemoved(displayId: Int) {
+                if (DEBUG) Log.d(TAG, "onDisplayRemoved: displayId=$displayId")
+                deleteDisplayResource(displayId)
+            }
+
+            override fun onDisplayChanged(displayId: Int) {
+                if (DEBUG) Log.d(TAG, "onDisplayChanged: displayId=$displayId")
+            }
+        })
+
+    fun destroy() {
+        displayResourceArray.valueIterator().forEach { displayResource ->
+            displayResource.cleanup()
+        }
+        displayResourceArray.clear()
+        displayManager.unregisterDisplayListener(displayListener)
+    }
+
+    fun getDisplayResource(displayId: Int): RESOURCE_TYPE? {
+        if (DEBUG) Log.d(TAG, "get: displayId=$displayId")
+        return displayResourceArray[displayId]
+    }
+
+    fun deleteDisplayResource(displayId: Int) {
+        if (DEBUG) Log.d(TAG, "delete: displayId=$displayId")
+        getDisplayResource(displayId)?.cleanup()
+        displayResourceArray.remove(displayId)
+    }
+
+    abstract class DisplayResource() {
+        abstract fun cleanup()
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
index 9b56fd4..1e857ca 100644
--- a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -62,6 +62,7 @@
 import com.android.launcher3.anim.SpringAnimationBuilder;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.MSDLPlayerWrapper;
 import com.android.quickstep.fallback.FallbackRecentsView;
 import com.android.quickstep.fallback.RecentsState;
 import com.android.quickstep.util.RectFSpringAnim;
@@ -102,9 +103,10 @@
 
     public FallbackSwipeHandler(Context context, RecentsAnimationDeviceState deviceState,
             TaskAnimationManager taskAnimationManager, GestureState gestureState, long touchTimeMs,
-            boolean continuingLastGesture, InputConsumerController inputConsumer) {
+            boolean continuingLastGesture, InputConsumerController inputConsumer,
+            MSDLPlayerWrapper msdlPlayerWrapper) {
         super(context, deviceState, taskAnimationManager, gestureState, touchTimeMs,
-                continuingLastGesture, inputConsumer, null);
+                continuingLastGesture, inputConsumer, msdlPlayerWrapper);
 
         mRunningOverHome = mGestureState.getRunningTask() != null
                 && mGestureState.getRunningTask().isHomeTask();
diff --git a/quickstep/src/com/android/quickstep/FallbackWindowInterface.java b/quickstep/src/com/android/quickstep/FallbackWindowInterface.java
index 832c093..f7836b0 100644
--- a/quickstep/src/com/android/quickstep/FallbackWindowInterface.java
+++ b/quickstep/src/com/android/quickstep/FallbackWindowInterface.java
@@ -51,33 +51,13 @@
  */
 public final class FallbackWindowInterface extends BaseWindowInterface{
 
-    private static FallbackWindowInterface INSTANCE;
-
     private final RecentsWindowManager mRecentsWindowManager;
 
-    /**
-     * This is only null before init() or after destroy()
-     */
-    @Nullable
-    public static FallbackWindowInterface getInstance(){
-        return INSTANCE;
-    }
-
-    public static void init(RecentsWindowManager recentsWindowManager) {
-        if (INSTANCE == null) {
-            INSTANCE = new FallbackWindowInterface(recentsWindowManager);
-        }
-    }
-
-    private FallbackWindowInterface(RecentsWindowManager recentsWindowManager) {
+    public FallbackWindowInterface(RecentsWindowManager recentsWindowManager) {
         super(DEFAULT, BACKGROUND_APP);
         mRecentsWindowManager = recentsWindowManager;
     }
 
-    public void destroy() {
-        INSTANCE = null;
-    }
-
     /** 2 */
     @Override
     public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect,
diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index ef6a09d..d193fee 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -292,8 +292,7 @@
             return;
         }
         LauncherOverlayManager om = launcher.getOverlayManager();
-        if (!SystemUiProxy.INSTANCE.get(launcher).getHomeVisibilityState().isHomeVisible()
-                || launcher.isForceInvisible()) {
+        if (!SystemUiProxy.INSTANCE.get(launcher).getHomeVisibilityState().isHomeVisible()) {
             om.hideOverlay(false /* animate */);
         } else {
             om.hideOverlay(150);
diff --git a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
index 4bd9ffb..c1e018d 100644
--- a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
+++ b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
@@ -16,6 +16,7 @@
 
 package com.android.quickstep;
 
+import static android.util.MathUtils.lerp;
 import static android.view.RemoteAnimationTarget.MODE_CLOSING;
 import static android.view.RemoteAnimationTarget.MODE_OPENING;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
@@ -51,11 +52,15 @@
 import android.window.BackProgressAnimator;
 import android.window.IBackAnimationHandoffHandler;
 import android.window.IOnBackInvokedCallback;
+
+import com.android.app.animation.Animations;
 import com.android.app.animation.Interpolators;
 import com.android.internal.policy.SystemBarUtils;
 import com.android.internal.view.AppearanceRegion;
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.Flags;
+import com.android.launcher3.LauncherState;
 import com.android.launcher3.QuickstepTransitionManager;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
@@ -65,6 +70,7 @@
 import com.android.launcher3.util.NavigationMode;
 import com.android.launcher3.widget.LauncherAppWidgetHostView;
 import com.android.quickstep.util.BackAnimState;
+import com.android.quickstep.util.ScalingWorkspaceRevealAnim;
 import com.android.systemui.shared.system.QuickStepContract;
 
 import java.lang.ref.WeakReference;
@@ -87,9 +93,12 @@
  */
 public class LauncherBackAnimationController {
     private static final int SCRIM_FADE_DURATION = 233;
-    private static final float MIN_WINDOW_SCALE = 0.85f;
+    private static final float MIN_WINDOW_SCALE =
+            Flags.predictiveBackToHomePolish() ? 0.75f : 0.85f;
     private static final float MAX_SCRIM_ALPHA_DARK = 0.8f;
     private static final float MAX_SCRIM_ALPHA_LIGHT = 0.2f;
+    private static final int MAX_BLUR_RADIUS = 20;
+    private static final int MIN_BLUR_RADIUS_PRE_COMMIT = 10;
 
     private final QuickstepTransitionManager mQuickstepTransitionManager;
     private final Matrix mTransformMatrix = new Matrix();
@@ -119,6 +128,7 @@
     private ValueAnimator mScrimAlphaAnimator;
     private float mScrimAlpha;
     private boolean mOverridingStatusBarFlags;
+    private int mLastBlurRadius = 0;
 
     private final ComponentCallbacks mComponentCallbacks = new ComponentCallbacks() {
         @Override
@@ -314,6 +324,17 @@
                 new RemoteAnimationTarget[]{ mBackTarget });
         setLauncherTargetViewVisible(false);
         mCurrentRect.set(mStartRect);
+        if (Flags.predictiveBackToHomePolish() && !mLauncher.getWorkspace().isOverlayShown()
+                && !mLauncher.isInState(LauncherState.ALL_APPS)) {
+            Animations.cancelOngoingAnimation(mLauncher.getWorkspace());
+            Animations.cancelOngoingAnimation(mLauncher.getHotseat());
+            if (Flags.predictiveBackToHomeBlur()) {
+                mLauncher.getDepthController().pauseBlursOnWindows(true);
+            }
+            mLauncher.getDepthController().stateDepth.setValue(
+                    LauncherState.BACKGROUND_APP.getDepth(mLauncher));
+            setLauncherScale(ScalingWorkspaceRevealAnim.MIN_SIZE);
+        }
         if (mScrimLayer == null) {
             addScrimLayer();
         }
@@ -328,6 +349,13 @@
         }
     }
 
+    private void setLauncherScale(float scale) {
+        mLauncher.getWorkspace().setScaleX(scale);
+        mLauncher.getWorkspace().setScaleY(scale);
+        mLauncher.getHotseat().setScaleX(scale);
+        mLauncher.getHotseat().setScaleY(scale);
+    }
+
     void addScrimLayer() {
         SurfaceControl parent = mLauncherTarget != null ? mLauncherTarget.leash : null;
         if (parent == null || !parent.isValid()) {
@@ -346,6 +374,7 @@
         final float[] colorComponents = new float[] { 0f, 0f, 0f };
         mScrimAlpha = (isDarkTheme)
                 ? MAX_SCRIM_ALPHA_DARK : MAX_SCRIM_ALPHA_LIGHT;
+        setBlur(MAX_BLUR_RADIUS);
         mTransaction
                 .setColor(mScrimLayer, colorComponents)
                 .setAlpha(mScrimLayer, mScrimAlpha)
@@ -372,6 +401,9 @@
         if (mScrimLayer == null) {
             // Scrim hasn't been attached yet. Let's attach it.
             addScrimLayer();
+        } else {
+            mLastBlurRadius = (int) lerp(MAX_BLUR_RADIUS, MIN_BLUR_RADIUS_PRE_COMMIT, progress);
+            setBlur(mLastBlurRadius);
         }
         float screenWidth = mStartRect.width();
         float screenHeight = mStartRect.height();
@@ -403,6 +435,12 @@
         customizeStatusBarAppearance(top > mStatusBarHeight / 2);
     }
 
+    private void setBlur(int blurRadius) {
+        if (Flags.predictiveBackToHomeBlur()) {
+            mTransaction.setBackgroundBlurRadius(mScrimLayer, blurRadius);
+        }
+    }
+
     /** Transform the target window to match the target rect. */
     private void applyTransform(RectF targetRect, float cornerRadius) {
         final float scale = targetRect.width() / mStartRect.width();
@@ -500,6 +538,14 @@
         if (mScrimLayer != null) {
             removeScrimLayer();
         }
+        if (Flags.predictiveBackToHomePolish() && !mLauncher.getWorkspace().isOverlayShown()
+                && !mLauncher.isInState(LauncherState.ALL_APPS)) {
+            setLauncherScale(ScalingWorkspaceRevealAnim.MAX_SIZE);
+            if (Flags.predictiveBackToHomeBlur()) {
+                mLauncher.getDepthController().pauseBlursOnWindows(false);
+            }
+        }
+        mLastBlurRadius = 0;
     }
 
     private void startTransitionAnimations(BackAnimState backAnim) {
@@ -513,6 +559,7 @@
             float value = (Float) animation.getAnimatedValue();
             if (mScrimLayer != null && mScrimLayer.isValid()) {
                 mTransaction.setAlpha(mScrimLayer, value * mScrimAlpha);
+                setBlur((int) lerp(mLastBlurRadius, 0, 1f - value));
                 applyTransaction();
             }
         });
diff --git a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
index 6087dc2..1827cc3 100644
--- a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
+++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
@@ -41,6 +41,7 @@
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
+import com.android.launcher3.util.MSDLPlayerWrapper;
 import com.android.launcher3.util.StableViewInfo;
 import com.android.launcher3.views.ClipIconView;
 import com.android.launcher3.views.FloatingIconView;
@@ -67,9 +68,10 @@
 
     public LauncherSwipeHandlerV2(Context context, RecentsAnimationDeviceState deviceState,
             TaskAnimationManager taskAnimationManager, GestureState gestureState, long touchTimeMs,
-            boolean continuingLastGesture, InputConsumerController inputConsumer) {
+            boolean continuingLastGesture, InputConsumerController inputConsumer,
+            MSDLPlayerWrapper msdlPlayerWrapper) {
         super(context, deviceState, taskAnimationManager, gestureState, touchTimeMs,
-                continuingLastGesture, inputConsumer, null);
+                continuingLastGesture, inputConsumer, msdlPlayerWrapper);
     }
 
 
@@ -329,7 +331,7 @@
         protected void playScalingRevealAnimation() {
             if (mContainer != null) {
                 new ScalingWorkspaceRevealAnim(mContainer, mSiblingAnimation,
-                        getWindowTargetRect()).start();
+                        getWindowTargetRect(), true /* playAlphaReveal */).start();
             }
         }
 
@@ -379,7 +381,7 @@
             if (mContainer != null) {
                 new ScalingWorkspaceRevealAnim(
                         mContainer, null /* siblingAnimation */,
-                        null /* windowTargetRect */).start();
+                        null /* windowTargetRect */, true /* playAlphaReveal */).start();
             }
         }
     }
diff --git a/quickstep/src/com/android/quickstep/OWNERS b/quickstep/src/com/android/quickstep/OWNERS
new file mode 100644
index 0000000..868e0ab
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/OWNERS
@@ -0,0 +1,9 @@
+# System Navigation team
+brianji@google.com
+jonmiranda@google.com
+jagrutdesai@google.com
+randypfohl@google.com
+saumyaprakash@google.com
+sukeshram@google.com
+twickham@google.com
+victortulias@google.com
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt b/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
index c09bf3e..c81edcd 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
@@ -233,7 +233,7 @@
         // When running task view is null we return last large taskView - typically focusView when
         // grid only is not enabled else last desktop task view.
         return if (recentsView.runningTaskView == null) {
-            recentsView.lastLargeTaskView ?: recentsView.getTaskViewAt(0)
+            recentsView.lastLargeTaskView ?: recentsView.getFirstTaskView()
         } else {
             if (
                 enableLargeDesktopWindowingTile() &&
diff --git a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
index 1f6c671..1fc0401 100644
--- a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
+++ b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
@@ -42,6 +42,7 @@
 import com.android.launcher3.Flags;
 import com.android.launcher3.R;
 import com.android.launcher3.util.SimpleBroadcastReceiver;
+import com.android.quickstep.fallback.window.RecentsDisplayModel;
 import com.android.quickstep.util.ActiveGestureProtoLogProxy;
 import com.android.systemui.shared.system.PackageManagerWrapper;
 
@@ -180,7 +181,9 @@
             // The default home app is a different launcher. Use the fallback Overview instead.
 
             if (Flags.enableLauncherOverviewInWindow() || Flags.enableFallbackOverviewInWindow()) {
-                mContainerInterface = FallbackWindowInterface.getInstance();
+                mContainerInterface =
+                        RecentsDisplayModel.getINSTANCE().get(mContext)
+                                .getFallbackWindowInterface(mDeviceState.getDisplayId());
             } else {
                 mContainerInterface = FallbackActivityInterface.INSTANCE;
             }
diff --git a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
index 2828a84..a5d2f3e 100644
--- a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -208,8 +208,10 @@
         OverviewComponentObserver observer = new OverviewComponentObserver(mContext, rads);
         try {
             RecentsViewContainer container = observer.getContainerInterface().getCreatedContainer();
+            WindowInsets insets = container == null
+                    ? null : container.getRootView().getRootWindowInsets();
 
-            return container == null ? null : container.getRootView().getRootWindowInsets();
+            return insets == null ? super.getWindowInsets() : insets;
         } finally {
             observer.onDestroy();
             rads.destroy();
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index 59cd639..17c17cc 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -19,6 +19,8 @@
 import static android.view.RemoteAnimationTarget.MODE_CLOSING;
 import static android.view.RemoteAnimationTarget.MODE_OPENING;
 
+import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE;
+import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE_RECREATE_TO_UPDATE_THEME;
 import static com.android.launcher3.QuickstepTransitionManager.RECENTS_LAUNCH_DURATION;
 import static com.android.launcher3.QuickstepTransitionManager.STATUS_BAR_TRANSITION_DURATION;
 import static com.android.launcher3.QuickstepTransitionManager.STATUS_BAR_TRANSITION_PRE_DELAY;
@@ -390,6 +392,32 @@
 
         // Set screen title for Talkback
         setTitle(R.string.accessibility_recent_apps);
+
+        restoreState(savedInstanceState);
+    }
+
+    @Override
+    protected void onSaveInstanceState(@NonNull Bundle outState) {
+        outState.putInt(RUNTIME_STATE, mStateManager.getState().ordinal);
+        super.onSaveInstanceState(outState);
+    }
+
+    /**
+     * Restores the previous state, if it exists.
+     *
+     * @param savedState The previous state.
+     */
+    private void restoreState(Bundle savedState) {
+        if (savedState == null) {
+            return;
+        }
+
+        if (savedState.getBoolean(RUNTIME_STATE_RECREATE_TO_UPDATE_THEME)) {
+            // RecentsState is only restored after theme changes.
+            int stateOrdinal = savedState.getInt(RUNTIME_STATE, RecentsState.DEFAULT.ordinal);
+            RecentsState recentsState = RecentsState.stateFromOrdinal(stateOrdinal);
+            mStateManager.goToState(recentsState, /*animated=*/false);
+        }
     }
 
     @Override
@@ -526,12 +554,6 @@
         return overviewCommandHelper == null || overviewCommandHelper.canStartHomeSafely();
     }
 
-    @NonNull
-    @Override
-    public TISBindHelper getTISBindHelper() {
-        return mTISBindHelper;
-    }
-
     @Override
     public boolean isRecentsViewVisible() {
         return getStateManager().getState().isRecentsViewVisible();
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 2991e64..6a25ecb 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -1478,10 +1478,11 @@
     }
 
     /** Call shell to move a task with given `taskId` to desktop  */
-    public void moveToDesktop(int taskId, DesktopModeTransitionSource transitionSource) {
+    public void moveToDesktop(int taskId, DesktopModeTransitionSource transitionSource,
+            @Nullable RemoteTransition transition) {
         if (mDesktopMode != null) {
             try {
-                mDesktopMode.moveToDesktop(taskId, transitionSource);
+                mDesktopMode.moveToDesktop(taskId, transitionSource, transition);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed call moveToDesktop", e);
             }
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index 0ea128a..e0d4ddd 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -47,6 +47,7 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.taskbar.TaskbarUIController;
 import com.android.launcher3.util.DisplayController;
+import com.android.quickstep.fallback.window.RecentsDisplayModel;
 import com.android.quickstep.fallback.window.RecentsWindowManager;
 import com.android.quickstep.util.ActiveGestureProtoLogProxy;
 import com.android.quickstep.util.SystemUiFlagUtils;
@@ -65,10 +66,11 @@
             SystemProperties.getBoolean("persist.wm.debug.shell_transit_rotate", false);
 
     private final Context mCtx;
-    private RecentsWindowManager mRecentsWindowsManager;
     private RecentsAnimationController mController;
     private RecentsAnimationCallbacks mCallbacks;
     private RecentsAnimationTargets mTargets;
+    private RecentsAnimationDeviceState mDeviceState;
+
     // Temporary until we can hook into gesture state events
     private GestureState mLastGestureState;
     private RemoteAnimationTarget[] mLastAppearedTaskTargets;
@@ -100,9 +102,9 @@
         }
     };
 
-    TaskAnimationManager(Context ctx, RecentsWindowManager manager) {
+    TaskAnimationManager(Context ctx, RecentsAnimationDeviceState deviceState) {
         mCtx = ctx;
-        mRecentsWindowsManager = manager;
+        mDeviceState = deviceState;
     }
     SystemUiProxy getSystemUiProxy() {
         return SystemUiProxy.INSTANCE.get(mCtx);
@@ -298,7 +300,9 @@
                         || Flags.enableLauncherOverviewInWindow())) {
             mRecentsAnimationStartPending = getSystemUiProxy().startRecentsActivity(intent, options,
                     mCallbacks, gestureState.useSyntheticRecentsTransition());
-            mRecentsWindowsManager.startRecentsWindow(mCallbacks);
+            RecentsDisplayModel.getINSTANCE().get(mCtx)
+                    .getRecentsWindowManager(mDeviceState.getDisplayId())
+                    .startRecentsWindow(mCallbacks);
         } else {
             options.setPendingIntentBackgroundActivityStartMode(
                     ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS);
diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
index 785666f..ab5e830 100644
--- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
@@ -317,7 +317,7 @@
             boolean hideForExistingMultiWindow = container.getDeviceProfile().isMultiWindowMode;
             boolean isLargeTile = deviceProfile.isTablet && taskView.isLargeTile();
             boolean isTaskInExpectedScrollPosition =
-                    recentsView.isTaskInExpectedScrollPosition(recentsView.indexOfChild(taskView));
+                    recentsView.isTaskInExpectedScrollPosition(taskView);
 
             if (notEnoughTasksToSplit || isTaskSplitNotSupported || hideForExistingMultiWindow
                     || (isLargeTile && isTaskInExpectedScrollPosition)) {
@@ -342,7 +342,7 @@
             final RecentsView recentsView = taskView.getRecentsView();
             boolean isLargeTile = deviceProfile.isTablet && taskView.isLargeTile();
             boolean isInExpectedScrollPosition =
-                    recentsView.isTaskInExpectedScrollPosition(recentsView.indexOfChild(taskView));
+                    recentsView.isTaskInExpectedScrollPosition(taskView);
             boolean shouldShowActionsButtonInstead =
                     isLargeTile && isInExpectedScrollPosition;
 
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index 084cede..dec36cf 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -108,7 +108,7 @@
      * opening remote target (which we don't get until onAnimationStart) will resolve to a TaskView.
      */
     public static TaskView findTaskViewToLaunch(
-            RecentsView recentsView, View v, RemoteAnimationTarget[] targets) {
+            RecentsView<?, ?> recentsView, View v, RemoteAnimationTarget[] targets) {
         if (v instanceof TaskView) {
             TaskView taskView = (TaskView) v;
             return recentsView.isTaskViewVisible(taskView) ? taskView : null;
@@ -121,8 +121,7 @@
             ComponentName componentName = itemInfo.getTargetComponent();
             int userId = itemInfo.user.getIdentifier();
             if (componentName != null) {
-                for (int i = 0; i < recentsView.getTaskViewCount(); i++) {
-                    TaskView taskView = recentsView.getTaskViewAt(i);
+                for (TaskView taskView : recentsView.getTaskViews()) {
                     if (recentsView.isTaskViewVisible(taskView)) {
                         Task.TaskKey key = taskView.getFirstTask().key;
                         if (componentName.equals(key.getComponent()) && userId == key.userId) {
@@ -562,7 +561,7 @@
      * Start recents to desktop animation
      */
     public static AnimatorSet composeRecentsDesktopLaunchAnimator(
-            @NonNull DesktopTaskView launchingTaskView,
+            @NonNull TaskView launchingTaskView,
             @NonNull StateManager stateManager, @Nullable DepthController depthController,
             @NonNull TransitionInfo transitionInfo,
             SurfaceControl.Transaction t, @NonNull Runnable finishCallback) {
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 50d4dab..9de96c7 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -83,13 +83,13 @@
 import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.LockedUserState;
+import com.android.launcher3.util.MSDLPlayerWrapper;
 import com.android.launcher3.util.NavigationMode;
 import com.android.launcher3.util.PluginManagerWrapper;
 import com.android.launcher3.util.SafeCloseable;
 import com.android.launcher3.util.ScreenOnTracker;
 import com.android.launcher3.util.TraceHelper;
 import com.android.quickstep.OverviewCommandHelper.CommandType;
-import com.android.quickstep.fallback.window.RecentsWindowManager;
 import com.android.quickstep.fallback.window.RecentsWindowSwipeHandler;
 import com.android.quickstep.inputconsumers.BubbleBarInputConsumer;
 import com.android.quickstep.inputconsumers.OneHandedModeInputConsumer;
@@ -612,6 +612,11 @@
         public void onToggleOverview() {
             mOverviewCommandHelper.addCommand(CommandType.TOGGLE);
         }
+
+        @Override
+        public void onHideOverview() {
+            mOverviewCommandHelper.addCommand(CommandType.HIDE);
+        }
     };
 
     private ActivityManagerWrapper mAM;
@@ -631,7 +636,6 @@
     private InputEventReceiver mInputEventReceiver;
 
     private TaskbarManager mTaskbarManager;
-    private RecentsWindowManager mRecentsWindowManager;
     private Function<GestureState, AnimatedFloat> mSwipeUpProxyProvider = i -> null;
     private AllAppsActionManager mAllAppsActionManager;
     private InputManager mInputManager;
@@ -668,9 +672,6 @@
         mDesktopAppLaunchTransitionManager =
                 new DesktopAppLaunchTransitionManager(this, SystemUiProxy.INSTANCE.get(this));
         mDesktopAppLaunchTransitionManager.registerTransitions();
-        if (Flags.enableLauncherOverviewInWindow() || Flags.enableFallbackOverviewInWindow()) {
-            mRecentsWindowManager = new RecentsWindowManager(this);
-        }
         mInputConsumer = InputConsumerController.getRecentsAnimationInputConsumer();
 
         // Call runOnUserUnlocked() before any other callbacks to ensure everything is initialized.
@@ -722,7 +723,7 @@
     public void onUserUnlocked() {
         Log.d(TAG, "onUserUnlocked: userId=" + getUserId()
                 + " instance=" + System.identityHashCode(this));
-        mTaskAnimationManager = new TaskAnimationManager(this, mRecentsWindowManager);
+        mTaskAnimationManager = new TaskAnimationManager(this, mDeviceState);
         mOverviewComponentObserver = new OverviewComponentObserver(this, mDeviceState);
         mOverviewCommandHelper = new OverviewCommandHelper(this,
                 mOverviewComponentObserver, mTaskAnimationManager);
@@ -829,10 +830,6 @@
         mTrackpadsConnected.clear();
 
         mTaskbarManager.destroy();
-
-        if (mRecentsWindowManager != null) {
-            mRecentsWindowManager.destroy();
-        }
         if (mDesktopAppLaunchTransitionManager != null) {
             mDesktopAppLaunchTransitionManager.unregisterTransitions();
         }
@@ -1274,20 +1271,20 @@
             GestureState gestureState, long touchTimeMs) {
         return new LauncherSwipeHandlerV2(this, mDeviceState, mTaskAnimationManager,
                 gestureState, touchTimeMs, mTaskAnimationManager.isRecentsAnimationRunning(),
-                mInputConsumer);
+                mInputConsumer, MSDLPlayerWrapper.INSTANCE.get(this));
     }
 
     private AbsSwipeUpHandler createFallbackSwipeHandler(
             GestureState gestureState, long touchTimeMs) {
         return new FallbackSwipeHandler(this, mDeviceState, mTaskAnimationManager,
                 gestureState, touchTimeMs, mTaskAnimationManager.isRecentsAnimationRunning(),
-                mInputConsumer);
+                mInputConsumer, MSDLPlayerWrapper.INSTANCE.get(this));
     }
 
     private AbsSwipeUpHandler createRecentsWindowSwipeHandler(
             GestureState gestureState, long touchTimeMs) {
         return new RecentsWindowSwipeHandler(this, mDeviceState, mTaskAnimationManager,
                 gestureState, touchTimeMs, mTaskAnimationManager.isRecentsAnimationRunning(),
-                mInputConsumer, mRecentsWindowManager);
+                mInputConsumer, MSDLPlayerWrapper.INSTANCE.get(this));
     }
 }
diff --git a/quickstep/src/com/android/quickstep/dagger/QuickStepModule.java b/quickstep/src/com/android/quickstep/dagger/QuickStepModule.java
index 9f6360b..a6feff0 100644
--- a/quickstep/src/com/android/quickstep/dagger/QuickStepModule.java
+++ b/quickstep/src/com/android/quickstep/dagger/QuickStepModule.java
@@ -20,7 +20,9 @@
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapperImpl;
 import com.android.launcher3.util.ApiWrapper;
 import com.android.launcher3.util.PluginManagerWrapper;
+import com.android.launcher3.util.window.WindowManagerProxy;
 import com.android.quickstep.contextualeducation.SystemContextualEduStatsManager;
+import com.android.quickstep.util.SystemWindowManagerProxy;
 
 import dagger.Binds;
 import dagger.Module;
@@ -32,4 +34,5 @@
     @Binds abstract ApiWrapper bindApiWrapper(SystemApiWrapper systemApiWrapper);
     @Binds abstract ContextualEduStatsManager bindContextualEduStatsManager(
             SystemContextualEduStatsManager manager);
+    @Binds abstract WindowManagerProxy bindWindowManagerProxy(SystemWindowManagerProxy proxy);
 }
diff --git a/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java b/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java
index b2670e8..4255372 100644
--- a/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java
+++ b/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java
@@ -20,6 +20,7 @@
 import com.android.launcher3.dagger.LauncherBaseAppComponent;
 import com.android.launcher3.model.WellbeingModel;
 import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.fallback.window.RecentsDisplayModel;
 import com.android.quickstep.util.AsyncClockEventDelegate;
 
 /**
@@ -37,4 +38,6 @@
     AsyncClockEventDelegate getAsyncClockEventDelegate();
 
     SystemUiProxy getSystemUiProxy();
+
+    RecentsDisplayModel getRecentsDisplayModel();
 }
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index daad6b7..9625d29 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -43,9 +43,9 @@
 import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource;
 import com.android.quickstep.BaseContainerInterface;
 import com.android.quickstep.FallbackActivityInterface;
-import com.android.quickstep.FallbackWindowInterface;
 import com.android.quickstep.GestureState;
 import com.android.quickstep.RotationTouchHelper;
+import com.android.quickstep.fallback.window.RecentsDisplayModel;
 import com.android.quickstep.util.GroupTask;
 import com.android.quickstep.util.SplitSelectStateController;
 import com.android.quickstep.util.TaskViewSimulator;
@@ -73,14 +73,15 @@
     }
 
     public FallbackRecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr, getContainerInterface());
+        super(context, attrs, defStyleAttr);
         mContainer.getStateManager().addStateListener(this);
     }
 
-    private static BaseContainerInterface<RecentsState, ?> getContainerInterface() {
+    @Override
+    public BaseContainerInterface<RecentsState, ?> getContainerInterface(int displayId) {
         return (Flags.enableFallbackOverviewInWindow() || Flags.enableLauncherOverviewInWindow())
-                ? FallbackWindowInterface.getInstance()
-                : FallbackActivityInterface.INSTANCE;
+                ? RecentsDisplayModel.getINSTANCE().get(mContext)
+                .getFallbackWindowInterface(displayId) : FallbackActivityInterface.INSTANCE;
     }
 
     @Override
@@ -180,7 +181,7 @@
         Task runningTask = runningTasks[0];
         if (mHomeTask != null && runningTask != null
                 && mHomeTask.key.id == runningTask.key.id
-                && getTaskViewCount() == 0 && mLoadPlanEverApplied) {
+                && !hasTaskViews() && mLoadPlanEverApplied) {
             // Do not add a stub task if we are running over home with empty recents, so that we
             // show the empty recents message instead of showing a stub task and later removing it.
             // Ignore empty task signal if applyLoadPlan has never run.
@@ -263,13 +264,13 @@
         }
 
         setFreezeViewVisibility(true);
-        if (mContainer.getDesktopVisibilityController() != null) {
-            mContainer.getDesktopVisibilityController().onLauncherStateChanged(toState);
-        }
     }
 
     @Override
     public void onStateTransitionComplete(RecentsState finalState) {
+        if (mContainer.getDesktopVisibilityController() != null) {
+            mContainer.getDesktopVisibilityController().onLauncherStateChanged(finalState);
+        }
         if (!finalState.isRecentsViewVisible()) {
             // Clean-up logic that occurs when recents is no longer in use/visible.
             reset();
diff --git a/quickstep/src/com/android/quickstep/fallback/RecentsState.java b/quickstep/src/com/android/quickstep/fallback/RecentsState.java
index 34783c7..c2e7536 100644
--- a/quickstep/src/com/android/quickstep/fallback/RecentsState.java
+++ b/quickstep/src/com/android/quickstep/fallback/RecentsState.java
@@ -45,6 +45,8 @@
     private static final int FLAG_TASK_THUMBNAIL_SPLASH = BaseState.getFlag(8);
     private static final int FLAG_DETACH_DESKTOP_CAROUSEL = BaseState.getFlag(9);
 
+    private static final RecentsState[] sAllStates = new RecentsState[6];
+
     public static final RecentsState DEFAULT = new RecentsState(0,
             FLAG_DISABLE_RESTORE | FLAG_CLEAR_ALL_BUTTON | FLAG_OVERVIEW_ACTIONS | FLAG_SHOW_AS_GRID
                     | FLAG_SCRIM | FLAG_LIVE_TILE | FLAG_RECENTS_VIEW_VISIBLE);
@@ -61,6 +63,11 @@
             FLAG_SHOW_AS_GRID | FLAG_SCRIM | FLAG_RECENTS_VIEW_VISIBLE | FLAG_CLOSE_POPUPS
                     | FLAG_DISABLE_RESTORE);
 
+    /** Returns the corresponding RecentsState from ordinal provided */
+    public static RecentsState stateFromOrdinal(int ordinal) {
+        return sAllStates[ordinal];
+    }
+
     public final int ordinal;
     private final int mFlags;
 
@@ -70,6 +77,7 @@
     public RecentsState(int id, int flags) {
         this.ordinal = id;
         this.mFlags = flags;
+        sAllStates[id] = this;
     }
 
 
diff --git a/quickstep/src/com/android/quickstep/fallback/window/RecentsDisplayModel.kt b/quickstep/src/com/android/quickstep/fallback/window/RecentsDisplayModel.kt
new file mode 100644
index 0000000..a9259d9
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/fallback/window/RecentsDisplayModel.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.fallback.window
+
+import android.content.Context
+import android.os.Handler
+import android.util.Log
+import android.view.Display
+import com.android.launcher3.Flags
+import com.android.launcher3.dagger.ApplicationContext
+import com.android.launcher3.dagger.LauncherAppSingleton
+import com.android.launcher3.util.DaggerSingletonObject
+import com.android.quickstep.DisplayModel
+import com.android.quickstep.FallbackWindowInterface
+import com.android.quickstep.dagger.QuickstepBaseAppComponent
+import com.android.quickstep.fallback.window.RecentsDisplayModel.RecentsDisplayResource
+import javax.inject.Inject
+
+@LauncherAppSingleton
+class RecentsDisplayModel @Inject constructor(@ApplicationContext context: Context) :
+    DisplayModel<RecentsDisplayResource>(context) {
+
+    companion object {
+        private const val TAG = "RecentsDisplayModel"
+        private const val DEBUG = false
+
+        @JvmStatic
+        val INSTANCE: DaggerSingletonObject<RecentsDisplayModel> =
+            DaggerSingletonObject<RecentsDisplayModel>(
+                QuickstepBaseAppComponent::getRecentsDisplayModel
+            )
+    }
+
+    init {
+        if (Flags.enableFallbackOverviewInWindow() || Flags.enableLauncherOverviewInWindow()) {
+            displayManager.registerDisplayListener(displayListener, Handler.getMain())
+            createDisplayResource(Display.DEFAULT_DISPLAY)
+        }
+    }
+
+    override fun createDisplayResource(displayId: Int) {
+        if (DEBUG) Log.d(TAG, "create: displayId=$displayId")
+        getDisplayResource(displayId)?.let {
+            return
+        }
+        val display = displayManager.getDisplay(displayId)
+        displayResourceArray[displayId] =
+            RecentsDisplayResource(displayId, context.createDisplayContext(display))
+    }
+
+    fun getRecentsWindowManager(displayId: Int): RecentsWindowManager? {
+        return getDisplayResource(displayId)?.recentsWindowManager
+    }
+
+    fun getFallbackWindowInterface(displayId: Int): FallbackWindowInterface? {
+        return getDisplayResource(displayId)?.fallbackWindowInterface
+    }
+
+    data class RecentsDisplayResource(var displayId: Int, var displayContext: Context) :
+        DisplayResource() {
+        val recentsWindowManager = RecentsWindowManager(displayContext)
+        val fallbackWindowInterface: FallbackWindowInterface =
+            FallbackWindowInterface(recentsWindowManager)
+
+        override fun cleanup() {
+            recentsWindowManager.destroy()
+        }
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowManager.kt b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowManager.kt
index f4c8c99..9bd7a19 100644
--- a/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowManager.kt
+++ b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowManager.kt
@@ -42,6 +42,7 @@
 import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory
 import com.android.launcher3.statemanager.StatefulContainer
 import com.android.launcher3.taskbar.TaskbarUIController
+import com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL
 import com.android.launcher3.testing.shared.TestProtocol.OVERVIEW_STATE_ORDINAL
 import com.android.launcher3.util.ContextTracker
 import com.android.launcher3.util.DisplayController
@@ -49,7 +50,6 @@
 import com.android.launcher3.util.SystemUiController
 import com.android.launcher3.views.BaseDragLayer
 import com.android.launcher3.views.ScrimView
-import com.android.quickstep.FallbackWindowInterface
 import com.android.quickstep.OverviewComponentObserver
 import com.android.quickstep.RecentsAnimationCallbacks
 import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener
@@ -97,6 +97,9 @@
 
         class RecentsWindowTracker : ContextTracker<RecentsWindowManager?>() {
             override fun isHomeStarted(context: RecentsWindowManager?): Boolean {
+                // if we need to change this block to use context in some way, we will need to
+                // refactor RecentsWindowTracker to be an instance (instead of a singleton) managed
+                // by RecentsDisplayModel. Otherwise bad things will occur.
                 return true
             }
         }
@@ -111,7 +114,7 @@
     private var layoutInflater: LayoutInflater = LayoutInflater.from(this).cloneInContext(this)
     private var stateManager: StateManager<RecentsState, RecentsWindowManager> =
         StateManager<RecentsState, RecentsWindowManager>(this, RecentsState.BG_LAUNCHER)
-    private var mSystemUiController: SystemUiController? = null
+    private var systemUiController: SystemUiController? = null
 
     private var dragLayer: RecentsDragLayer<RecentsWindowManager>? = null
     private var windowView: View? = null
@@ -124,7 +127,7 @@
     private var tisBindHelper: TISBindHelper = TISBindHelper(this) {}
 
     // Callback array that corresponds to events defined in @ActivityEvent
-    private val mEventCallbacks =
+    private val eventCallbacks =
         listOf(RunnableList(), RunnableList(), RunnableList(), RunnableList())
     private var onInitListener: Predicate<Boolean>? = null
 
@@ -151,14 +154,12 @@
         }
 
     init {
-        FallbackWindowInterface.init(this)
         TaskStackChangeListeners.getInstance().registerTaskStackListener(taskStackChangeListener)
     }
 
     override fun destroy() {
         super.destroy()
         cleanupRecentsWindow()
-        FallbackWindowInterface.getInstance()?.destroy()
         TaskStackChangeListeners.getInstance().unregisterTaskStackListener(taskStackChangeListener)
         callbacks?.removeListener(recentsAnimationListener)
         recentsWindowTracker.onContextDestroyed(this)
@@ -182,7 +183,7 @@
     }
 
     private fun startHomeInternal() {
-        val runner = LauncherAnimationRunner(mainThreadHandler, mAnimationToHomeFactory, true)
+        val runner = LauncherAnimationRunner(mainThreadHandler, animationToHomeFactory, true)
         val options =
             ActivityOptions.makeRemoteAnimation(
                 RemoteAnimationAdapter(runner, HOME_APPEAR_DURATION, 0),
@@ -196,7 +197,7 @@
         stateManager.moveToRestState()
     }
 
-    private val mAnimationToHomeFactory =
+    private val animationToHomeFactory =
         RemoteAnimationFactory {
             _: Int,
             appTargets: Array<RemoteAnimationTarget>?,
@@ -287,7 +288,7 @@
         actionsView?.updateDimension(getDeviceProfile(), recentsView?.lastComputedTaskSize)
         actionsView?.updateVerticalMargin(DisplayController.getNavigationMode(this))
 
-        mSystemUiController = SystemUiController(windowView)
+        systemUiController = SystemUiController(windowView)
         recentsWindowTracker.handleCreate(this)
 
         this.callbacks = callbacks
@@ -321,10 +322,6 @@
         return taskbarUIController
     }
 
-    override fun getTISBindHelper(): TISBindHelper {
-        return tisBindHelper
-    }
-
     fun registerInitListener(onInitListener: Predicate<Boolean>) {
         this.onInitListener = onInitListener
     }
@@ -357,8 +354,11 @@
         if (state == HOME || state == BG_LAUNCHER) {
             cleanupRecentsWindow()
         }
-        if (state === DEFAULT) {
-            AccessibilityManagerCompat.sendStateEventToTest(baseContext, OVERVIEW_STATE_ORDINAL)
+        when (state) {
+            HOME ->
+                AccessibilityManagerCompat.sendStateEventToTest(baseContext, NORMAL_STATE_ORDINAL)
+            DEFAULT ->
+                AccessibilityManagerCompat.sendStateEventToTest(baseContext, OVERVIEW_STATE_ORDINAL)
         }
     }
 
@@ -376,10 +376,10 @@
     }
 
     override fun getSystemUiController(): SystemUiController? {
-        if (mSystemUiController == null) {
-            mSystemUiController = SystemUiController(rootView)
+        if (systemUiController == null) {
+            systemUiController = SystemUiController(rootView)
         }
-        return mSystemUiController
+        return systemUiController
     }
 
     override fun getContext(): Context {
@@ -430,12 +430,12 @@
 
     /** Adds a callback for the provided activity event */
     override fun addEventCallback(@BaseActivity.ActivityEvent event: Int, callback: Runnable?) {
-        mEventCallbacks[event].add(callback)
+        eventCallbacks[event].add(callback)
     }
 
     /** Removes a previously added callback */
     override fun removeEventCallback(@BaseActivity.ActivityEvent event: Int, callback: Runnable?) {
-        mEventCallbacks[event].remove(callback)
+        eventCallbacks[event].remove(callback)
     }
 
     override fun runOnBindToTouchInteractionService(r: Runnable?) {
diff --git a/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowSwipeHandler.java b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowSwipeHandler.java
index be71385..afc8879 100644
--- a/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowSwipeHandler.java
@@ -62,6 +62,7 @@
 import com.android.launcher3.anim.SpringAnimationBuilder;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.MSDLPlayerWrapper;
 import com.android.quickstep.AbsSwipeUpHandler;
 import com.android.quickstep.GestureState;
 import com.android.quickstep.RecentsAnimationController;
@@ -100,6 +101,8 @@
     private static StaticMessageReceiver sMessageReceiver = null;
 
     private FallbackHomeAnimationFactory mActiveAnimationFactory;
+    private final RecentsDisplayModel mRecentsDisplayModel;
+
     private final boolean mRunningOverHome;
 
     private final Matrix mTmpMatrix = new Matrix();
@@ -110,10 +113,11 @@
     public RecentsWindowSwipeHandler(Context context, RecentsAnimationDeviceState deviceState,
             TaskAnimationManager taskAnimationManager, GestureState gestureState, long touchTimeMs,
             boolean continuingLastGesture, InputConsumerController inputConsumer,
-            RecentsWindowManager recentsWindowManager) {
+            MSDLPlayerWrapper msdlPlayerWrapper) {
         super(context, deviceState, taskAnimationManager, gestureState, touchTimeMs,
-                continuingLastGesture, inputConsumer, recentsWindowManager);
+                continuingLastGesture, inputConsumer, msdlPlayerWrapper);
 
+        mRecentsDisplayModel = RecentsDisplayModel.getINSTANCE().get(context);
         mRunningOverHome = mGestureState.getRunningTask() != null
                 && mGestureState.getRunningTask().isHomeTask();
 
@@ -159,7 +163,11 @@
         boolean fromHomeToHome = mRunningOverHome
                 && endTarget == GestureState.GestureEndTarget.HOME;
         if (fromHomeToHome) {
-            mRecentsWindowManager.startHome(/* finishRecentsAnimation= */ false);
+            RecentsWindowManager manager =
+                    mRecentsDisplayModel.getRecentsWindowManager(mDeviceState.getDisplayId());
+            if (manager != null) {
+                manager.startHome(/* finishRecentsAnimation= */ false);
+            }
         }
         super.animateGestureEnd(
                 startShift,
@@ -220,7 +228,11 @@
             // the PiP task appearing.
             recentsCallback = () -> {
                 callback.run();
-                mRecentsWindowManager.startHome();
+                RecentsWindowManager manager =
+                        mRecentsDisplayModel.getRecentsWindowManager(mDeviceState.getDisplayId());
+                if (manager != null) {
+                    manager.startHome();
+                }
             };
         } else {
             recentsCallback = callback;
diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
index be7f8e5..7fe4278 100644
--- a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
@@ -65,11 +65,6 @@
     }
 
     @Override
-    public int getSpokenIntroductionSubtitle() {
-        return R.string.back_gesture_spoken_intro_subtitle;
-    }
-
-    @Override
     public int getSuccessFeedbackTitle() {
         return R.string.gesture_tutorial_nice;
     }
diff --git a/quickstep/src/com/android/quickstep/interaction/EdgeBackGestureHandler.java b/quickstep/src/com/android/quickstep/interaction/EdgeBackGestureHandler.java
index 700fbf8..72bff7f 100644
--- a/quickstep/src/com/android/quickstep/interaction/EdgeBackGestureHandler.java
+++ b/quickstep/src/com/android/quickstep/interaction/EdgeBackGestureHandler.java
@@ -29,9 +29,9 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.testing.shared.ResourceUtils;
-import com.android.launcher3.util.DisplayController;
 
 /**
  * Utility class to handle edge swipes for back gestures.
@@ -45,6 +45,7 @@
             "gestures.back_timeout", 250);
 
     private final Context mContext;
+    private final DeviceProfile mDeviceProfile;
 
     private final Point mDisplaySize = new Point();
 
@@ -89,9 +90,10 @@
                 }
             };
 
-    EdgeBackGestureHandler(Context context) {
+    EdgeBackGestureHandler(Context context, DeviceProfile deviceProfile) {
         final Resources res = context.getResources();
         mContext = context;
+        mDeviceProfile = deviceProfile;
 
         mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
         mLongPressTimeout = Math.min(MAX_LONG_PRESS_TIMEOUT,
@@ -116,8 +118,7 @@
             // Add a nav bar panel window.
             mEdgeBackPanel = new EdgeBackGesturePanel(mContext, parent, createLayoutParams());
             mEdgeBackPanel.setBackCallback(mBackCallback);
-            Point currentSize = DisplayController.INSTANCE.get(mContext).getInfo().currentSize;
-            mDisplaySize.set(currentSize.x, currentSize.y);
+            mDisplaySize.set(mDeviceProfile.widthPx, mDeviceProfile.heightPx);
             mEdgeBackPanel.setDisplaySize(mDisplaySize);
         }
     }
diff --git a/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
index bc5cc15..0365f89 100644
--- a/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
+++ b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
@@ -115,22 +115,13 @@
 
     private void initWindowInsets() {
         View root = findViewById(android.R.id.content);
-        root.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
-            @Override
-            public void onLayoutChange(View v, int left, int top, int right, int bottom,
-                    int oldLeft, int oldTop, int oldRight, int oldBottom) {
-                updateExclusionRects(root);
-            }
-        });
+        root.addOnLayoutChangeListener(
+                (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) ->
+                        updateExclusionRects(root));
 
         // Return CONSUMED if you don't want want the window insets to keep being
         // passed down to descendant views.
-        root.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {
-            @Override
-            public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
-                return WindowInsets.CONSUMED;
-            }
-        });
+        root.setOnApplyWindowInsetsListener((v, insets) -> WindowInsets.CONSUMED);
     }
 
     private void updateExclusionRects(View rootView) {
diff --git a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java
index bf4eaf2..b059695 100644
--- a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java
@@ -58,11 +58,6 @@
     }
 
     @Override
-    public int getSpokenIntroductionSubtitle() {
-        return R.string.home_gesture_spoken_intro_subtitle;
-    }
-
-    @Override
     public int getSuccessFeedbackTitle() {
         return R.string.home_gesture_tutorial_success;
     }
diff --git a/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
index c00f508..fdbd509 100644
--- a/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
+++ b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
@@ -33,6 +33,7 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.testing.shared.ResourceUtils;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.NavigationMode;
@@ -57,15 +58,16 @@
     @Nullable
     private NavBarGestureAttemptCallback mGestureCallback;
 
-    NavBarGestureHandler(Context context) {
+    NavBarGestureHandler(Context context, DeviceProfile deviceProfile) {
         mContext = context;
-        DisplayController.Info displayInfo = DisplayController.INSTANCE.get(mContext).getInfo();
-        Point currentSize = displayInfo.currentSize;
-        mDisplaySize.set(currentSize.x, currentSize.y);
-        mSwipeUpTouchTracker =
-                new TriggerSwipeUpTouchTracker(context, true /*disableHorizontalSwipe*/,
-                        new NavBarPosition(NavigationMode.NO_BUTTON, displayInfo),
-                        this);
+        mDisplaySize.set(deviceProfile.widthPx, deviceProfile.heightPx);
+        mSwipeUpTouchTracker = new TriggerSwipeUpTouchTracker(
+                context,
+                /* disableHorizontalSwipe= */ true,
+                new NavBarPosition(
+                        NavigationMode.NO_BUTTON,
+                        DisplayController.INSTANCE.get(mContext).getInfo()),
+                /* onSwipeUp= */ this);
         mMotionPauseDetector = new MotionPauseDetector(context);
 
         final Resources resources = context.getResources();
diff --git a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java
index e45f8d8..ff0d6d1 100644
--- a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java
@@ -73,11 +73,6 @@
     }
 
     @Override
-    public int getSpokenIntroductionSubtitle() {
-        return R.string.overview_gesture_spoken_intro_subtitle;
-    }
-
-    @Override
     public int getSuccessFeedbackTitle() {
         return R.string.overview_gesture_tutorial_success;
     }
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialController.java b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
index 9510a05..7d14a3e 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
@@ -318,11 +318,6 @@
     }
 
     @StringRes
-    public int getSpokenIntroductionSubtitle() {
-        return NO_ID;
-    }
-
-    @StringRes
     public int getSuccessFeedbackSubtitle() {
         return NO_ID;
     }
@@ -401,17 +396,13 @@
                 isGestureSuccessful
                         ? getSuccessFeedbackTitle() : R.string.gesture_tutorial_try_again,
                 subtitleResId,
-                NO_ID,
-                isGestureSuccessful,
-                false);
+                isGestureSuccessful);
     }
 
     void showFeedback(
             int titleResId,
             int subtitleResId,
-            int spokenSubtitleResId,
-            boolean isGestureSuccessful,
-            boolean useGestureAnimationDelay) {
+            boolean isGestureSuccessful) {
         mFeedbackTitleView.removeCallbacks(mTitleViewCallback);
         if (mFeedbackViewCallback != null) {
             mFeedbackView.removeCallbacks(mFeedbackViewCallback);
@@ -512,7 +503,6 @@
             if (mTutorialType == TutorialType.BACK_NAVIGATION) {
                 resetViewsForBackGesture();
             }
-
         }
 
         mGestureCompleted = false;
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java
index 2ff2c83..8174e13 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java
@@ -48,6 +48,7 @@
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.quickstep.interaction.TutorialController.TutorialType;
@@ -175,8 +176,10 @@
         Bundle args = savedInstanceState != null ? savedInstanceState : getArguments();
         mTutorialType = (TutorialType) args.getSerializable(KEY_TUTORIAL_TYPE);
         mGestureComplete = args.getBoolean(KEY_GESTURE_COMPLETE, false);
-        mEdgeBackGestureHandler = new EdgeBackGestureHandler(getContext());
-        mNavBarGestureHandler = new NavBarGestureHandler(getContext());
+        DeviceProfile deviceProfile = LauncherAppState.getInstance(getContext())
+                .getInvariantDeviceProfile().getDeviceProfile(getContext());
+        mEdgeBackGestureHandler = new EdgeBackGestureHandler(getContext(), deviceProfile);
+        mNavBarGestureHandler = new NavBarGestureHandler(getContext(), deviceProfile);
 
         mDeviceProfile = InvariantDeviceProfile.INSTANCE.get(getContext())
                 .getDeviceProfile(getContext());
@@ -212,7 +215,6 @@
     public View onCreateView(
             @NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
         super.onCreateView(inflater, container, savedInstanceState);
-
         mRootView = (RootSandboxLayout) inflater.inflate(
                 R.layout.redesigned_gesture_tutorial_fragment,
                 container,
@@ -268,9 +270,7 @@
             mTutorialController.showFeedback(
                     introTitleResId,
                     introSubtitleResId,
-                    mTutorialController.getSpokenIntroductionSubtitle(),
-                    false,
-                    true);
+                    /* isGestureSuccessful= */ false);
             mIntroductionShown = true;
         }
     }
diff --git a/quickstep/src/com/android/quickstep/recents/data/RecentTasksRepository.kt b/quickstep/src/com/android/quickstep/recents/data/RecentTasksRepository.kt
index 3b59864..53969c5 100644
--- a/quickstep/src/com/android/quickstep/recents/data/RecentTasksRepository.kt
+++ b/quickstep/src/com/android/quickstep/recents/data/RecentTasksRepository.kt
@@ -37,6 +37,12 @@
     fun getThumbnailById(taskId: Int): Flow<ThumbnailData?>
 
     /**
+     * Gets the [ThumbnailData] associated with a task that has id [taskId]. Flow will settle on
+     * null if the task was not found or is invisible.
+     */
+    fun getCurrentThumbnailById(taskId: Int): ThumbnailData?
+
+    /**
      * Sets the tasks that are visible, indicating that properties relating to visuals need to be
      * populated e.g. icons/thumbnails etc.
      */
diff --git a/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt b/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
index 6c627ef..8c26d8f 100644
--- a/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
+++ b/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
@@ -72,6 +72,8 @@
     override fun getThumbnailById(taskId: Int) =
         getTaskDataById(taskId).map { it?.thumbnail }.distinctUntilChangedBy { it?.snapshotId }
 
+    override fun getCurrentThumbnailById(taskId: Int) = tasks.value[taskId]?.thumbnail
+
     override fun setVisibleTasks(visibleTaskIdList: Set<Int>) {
         val tasksNoLongerVisible = taskRequests.keys.subtract(visibleTaskIdList)
         val newlyVisibleTasks = visibleTaskIdList.subtract(taskRequests.keys)
@@ -94,7 +96,7 @@
         taskRequests[taskId] =
             Pair(
                 task.key,
-                recentsCoroutineScope.launch {
+                recentsCoroutineScope.launch(dispatcherProvider.main) {
                     Log.i(TAG, "requestTaskData: $taskId")
                     fetchIcon(task)
                     fetchThumbnail(task)
@@ -132,7 +134,7 @@
             task.key,
             object : TaskIconChangedCallback {
                 override fun onTaskIconChanged() {
-                    recentsCoroutineScope.launch {
+                    recentsCoroutineScope.launch(dispatcherProvider.main) {
                         updateIcon(task.key.id, getIconFromDataSource(task))
                     }
                 }
@@ -150,7 +152,7 @@
                 }
 
                 override fun onHighResLoadingStateChanged() {
-                    recentsCoroutineScope.launch {
+                    recentsCoroutineScope.launch(dispatcherProvider.main) {
                         updateThumbnail(task.key.id, getThumbnailFromDataSource(task))
                     }
                 }
diff --git a/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt b/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
index b78e214..95ecbe9 100644
--- a/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
+++ b/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
@@ -39,7 +39,6 @@
 import com.android.systemui.shared.recents.model.Task
 import kotlinx.coroutines.CoroutineName
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.SupervisorJob
 
 internal typealias RecentsScopeId = String
@@ -58,10 +57,13 @@
     private fun startDefaultScope(appContext: Context) {
         createScope(DEFAULT_SCOPE_ID).apply {
             set(RecentsViewData::class.java.simpleName, RecentsViewData())
+            val dispatcherProvider: DispatcherProvider = ProductionDispatchers
             val recentsCoroutineScope =
-                CoroutineScope(SupervisorJob() + Dispatchers.Main + CoroutineName("RecentsView"))
+                CoroutineScope(
+                    SupervisorJob() + dispatcherProvider.unconfined + CoroutineName("RecentsView")
+                )
             set(CoroutineScope::class.java.simpleName, recentsCoroutineScope)
-            set(DispatcherProvider::class.java.simpleName, ProductionDispatchers)
+            set(DispatcherProvider::class.java.simpleName, dispatcherProvider)
             val recentsModel = RecentsModel.INSTANCE.get(appContext)
             val taskVisualsChangedDelegate =
                 TaskVisualsChangedDelegateImpl(
@@ -170,18 +172,6 @@
         log("linked scopes: ${getScope(scopeId).scopeIdsLinked}")
         val instance: Any =
             when (modelClass) {
-                RecentTasksRepository::class.java -> {
-                    with(RecentsModel.INSTANCE.get(appContext)) {
-                        TasksRepository(
-                            this,
-                            thumbnailCache,
-                            iconCache,
-                            get(),
-                            get(),
-                            ProductionDispatchers,
-                        )
-                    }
-                }
                 RecentsViewData::class.java -> RecentsViewData()
                 TaskContainerData::class.java -> TaskContainerData()
                 TaskThumbnailViewData::class.java -> TaskThumbnailViewData()
diff --git a/quickstep/src/com/android/quickstep/recents/usecase/GetThumbnailPositionUseCase.kt b/quickstep/src/com/android/quickstep/recents/usecase/GetThumbnailPositionUseCase.kt
index f060d7d..bea1d07 100644
--- a/quickstep/src/com/android/quickstep/recents/usecase/GetThumbnailPositionUseCase.kt
+++ b/quickstep/src/com/android/quickstep/recents/usecase/GetThumbnailPositionUseCase.kt
@@ -24,18 +24,17 @@
 import com.android.quickstep.recents.usecase.ThumbnailPositionState.MatrixScaling
 import com.android.quickstep.recents.usecase.ThumbnailPositionState.MissingThumbnail
 import com.android.systemui.shared.recents.utilities.PreviewPositionHelper
-import kotlinx.coroutines.flow.firstOrNull
 
 /** Use case for retrieving [Matrix] for positioning Thumbnail in a View */
 class GetThumbnailPositionUseCase(
     private val deviceProfileRepository: RecentsDeviceProfileRepository,
     private val rotationStateRepository: RecentsRotationStateRepository,
     private val tasksRepository: RecentTasksRepository,
-    private val previewPositionHelper: PreviewPositionHelper = PreviewPositionHelper()
+    private val previewPositionHelper: PreviewPositionHelper = PreviewPositionHelper(),
 ) {
-    suspend fun run(taskId: Int, width: Int, height: Int, isRtl: Boolean): ThumbnailPositionState {
+    fun run(taskId: Int, width: Int, height: Int, isRtl: Boolean): ThumbnailPositionState {
         val thumbnailData =
-            tasksRepository.getThumbnailById(taskId).firstOrNull() ?: return MissingThumbnail
+            tasksRepository.getCurrentThumbnailById(taskId) ?: return MissingThumbnail
         val thumbnail = thumbnailData.thumbnail ?: return MissingThumbnail
         previewPositionHelper.updateThumbnailMatrix(
             Rect(0, 0, thumbnail.width, thumbnail.height),
@@ -44,11 +43,11 @@
             height,
             deviceProfileRepository.getRecentsDeviceProfile().isLargeScreen,
             rotationStateRepository.getRecentsRotationState().activityRotation,
-            isRtl
+            isRtl,
         )
         return MatrixScaling(
             previewPositionHelper.matrix,
-            previewPositionHelper.isOrientationChanged
+            previewPositionHelper.isOrientationChanged,
         )
     }
 }
diff --git a/quickstep/src/com/android/quickstep/recents/usecase/GetThumbnailUseCase.kt b/quickstep/src/com/android/quickstep/recents/usecase/GetThumbnailUseCase.kt
index 3aa808e..b9e9e02 100644
--- a/quickstep/src/com/android/quickstep/recents/usecase/GetThumbnailUseCase.kt
+++ b/quickstep/src/com/android/quickstep/recents/usecase/GetThumbnailUseCase.kt
@@ -18,13 +18,9 @@
 
 import android.graphics.Bitmap
 import com.android.quickstep.recents.data.RecentTasksRepository
-import kotlinx.coroutines.flow.firstOrNull
-import kotlinx.coroutines.runBlocking
 
 /** Use case for retrieving thumbnail. */
 class GetThumbnailUseCase(private val taskRepository: RecentTasksRepository) {
     /** Returns the latest thumbnail associated with [taskId] if loaded, or null otherwise */
-    fun run(taskId: Int): Bitmap? = runBlocking {
-        taskRepository.getThumbnailById(taskId).firstOrNull()?.thumbnail
-    }
+    fun run(taskId: Int): Bitmap? = taskRepository.getCurrentThumbnailById(taskId)?.thumbnail
 }
diff --git a/quickstep/src/com/android/quickstep/recents/usecase/SysUiStatusNavFlagsUseCase.kt b/quickstep/src/com/android/quickstep/recents/usecase/SysUiStatusNavFlagsUseCase.kt
index 1d19c7d..5be5f4a 100644
--- a/quickstep/src/com/android/quickstep/recents/usecase/SysUiStatusNavFlagsUseCase.kt
+++ b/quickstep/src/com/android/quickstep/recents/usecase/SysUiStatusNavFlagsUseCase.kt
@@ -22,14 +22,11 @@
 import com.android.launcher3.util.SystemUiController.FLAG_LIGHT_NAV
 import com.android.launcher3.util.SystemUiController.FLAG_LIGHT_STATUS
 import com.android.quickstep.recents.data.RecentTasksRepository
-import kotlinx.coroutines.flow.firstOrNull
-import kotlinx.coroutines.runBlocking
 
 /** UseCase to calculate flags for status bar and navigation bar */
 class SysUiStatusNavFlagsUseCase(private val taskRepository: RecentTasksRepository) {
     fun getSysUiStatusNavFlags(taskId: Int): Int {
-        val thumbnailData =
-            runBlocking { taskRepository.getThumbnailById(taskId).firstOrNull() } ?: return 0
+        val thumbnailData = taskRepository.getCurrentThumbnailById(taskId) ?: return 0
 
         val thumbnailAppearance = thumbnailData.appearance
         var flags = 0
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
index 0c783d3..e334695 100644
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
@@ -24,11 +24,12 @@
 import android.util.Log
 import android.view.View
 import android.view.ViewOutlineProvider
+import android.widget.FrameLayout
 import androidx.annotation.ColorInt
-import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.core.view.isInvisible
 import com.android.launcher3.R
 import com.android.launcher3.util.ViewPool
+import com.android.launcher3.util.coroutines.DispatcherProvider
 import com.android.quickstep.recents.di.RecentsDependencies
 import com.android.quickstep.recents.di.get
 import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.BackgroundOnly
@@ -45,8 +46,12 @@
 import kotlinx.coroutines.cancel
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
 
-class TaskThumbnailView : ConstraintLayout, ViewPool.Reusable {
+class TaskThumbnailView : FrameLayout, ViewPool.Reusable {
+    private val recentsCoroutineScope: CoroutineScope = RecentsDependencies.get()
+    private val dispatcherProvider: DispatcherProvider = RecentsDependencies.get()
+
     private lateinit var viewData: TaskThumbnailViewData
     private lateinit var viewModel: TaskThumbnailViewModel
 
@@ -119,7 +124,9 @@
 
     override fun onDetachedFromWindow() {
         super.onDetachedFromWindow()
-        viewAttachedScope.cancel("TaskThumbnailView detaching from window")
+        recentsCoroutineScope.launch(dispatcherProvider.background) {
+            viewAttachedScope.cancel("TaskThumbnailView detaching from window")
+        }
     }
 
     override fun onRecycle() {
diff --git a/quickstep/src/com/android/quickstep/task/util/TaskOverlayHelper.kt b/quickstep/src/com/android/quickstep/task/util/TaskOverlayHelper.kt
index 203177a..e6c8d27 100644
--- a/quickstep/src/com/android/quickstep/task/util/TaskOverlayHelper.kt
+++ b/quickstep/src/com/android/quickstep/task/util/TaskOverlayHelper.kt
@@ -18,6 +18,7 @@
 
 import android.util.Log
 import android.view.View.OnLayoutChangeListener
+import com.android.launcher3.util.coroutines.DispatcherProvider
 import com.android.quickstep.TaskOverlayFactory
 import com.android.quickstep.recents.di.RecentsDependencies
 import com.android.quickstep.recents.di.get
@@ -33,12 +34,15 @@
 import kotlinx.coroutines.cancel
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
 
 /**
  * Helper for [TaskOverlayFactory.TaskOverlay] to interact with [TaskOverlayViewModel], this helper
  * should merge with [TaskOverlayFactory.TaskOverlay] when it's migrated to MVVM.
  */
 class TaskOverlayHelper(val task: Task, val overlay: TaskOverlayFactory.TaskOverlay<*>) {
+    private val recentsCoroutineScope: CoroutineScope = RecentsDependencies.get()
+    private val dispatcherProvider: DispatcherProvider = RecentsDependencies.get()
     private lateinit var overlayInitializedScope: CoroutineScope
     private var uiState: TaskOverlayUiState = Disabled
 
@@ -101,7 +105,9 @@
     }
 
     fun destroy() {
-        overlayInitializedScope.cancel()
+        recentsCoroutineScope.launch(dispatcherProvider.background) {
+            overlayInitializedScope.cancel("TaskOverlay being destroyed")
+        }
         uiState = Disabled
         overlay.snapshotView.removeOnLayoutChangeListener(snapshotLayoutChangeListener)
         reset()
diff --git a/quickstep/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModel.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModel.kt
index 4e13d1c..14359db 100644
--- a/quickstep/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModel.kt
+++ b/quickstep/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModel.kt
@@ -28,7 +28,6 @@
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.runBlocking
 
 /** View model for TaskOverlay */
 class TaskOverlayViewModel(
@@ -41,7 +40,7 @@
         combine(
                 recentsViewData.overlayEnabled,
                 recentsViewData.settledFullyVisibleTaskIds.map { it.contains(task.key.id) },
-                recentTasksRepository.getThumbnailById(task.key.id)
+                recentTasksRepository.getThumbnailById(task.key.id),
             ) { isOverlayEnabled, isFullyVisible, thumbnailData ->
                 if (isOverlayEnabled && isFullyVisible) {
                     Enabled(
@@ -55,24 +54,22 @@
             .distinctUntilChanged()
 
     fun getThumbnailPositionState(width: Int, height: Int, isRtl: Boolean): ThumbnailPositionState {
-        return runBlocking {
-            val matrix: Matrix
-            val isRotated: Boolean
-            when (
-                val thumbnailPositionState =
-                    getThumbnailPositionUseCase.run(task.key.id, width, height, isRtl)
-            ) {
-                is MatrixScaling -> {
-                    matrix = thumbnailPositionState.matrix
-                    isRotated = thumbnailPositionState.isRotated
-                }
-                is MissingThumbnail -> {
-                    matrix = Matrix.IDENTITY_MATRIX
-                    isRotated = false
-                }
+        val matrix: Matrix
+        val isRotated: Boolean
+        when (
+            val thumbnailPositionState =
+                getThumbnailPositionUseCase.run(task.key.id, width, height, isRtl)
+        ) {
+            is MatrixScaling -> {
+                matrix = thumbnailPositionState.matrix
+                isRotated = thumbnailPositionState.isRotated
             }
-            ThumbnailPositionState(matrix, isRotated)
+            is MissingThumbnail -> {
+                matrix = Matrix.IDENTITY_MATRIX
+                isRotated = false
+            }
         }
+        return ThumbnailPositionState(matrix, isRotated)
     }
 
     data class ThumbnailPositionState(val matrix: Matrix, val isRotated: Boolean)
diff --git a/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModelImpl.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModelImpl.kt
index e5bad67..b5b2fc9 100644
--- a/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModelImpl.kt
+++ b/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModelImpl.kt
@@ -44,7 +44,6 @@
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.runBlocking
 
 @OptIn(ExperimentalCoroutinesApi::class)
 class TaskThumbnailViewModelImpl(
@@ -109,17 +108,14 @@
         splashProgress.value = splashAlphaUseCase.execute(taskId)
     }
 
-    override fun getThumbnailPositionState(width: Int, height: Int, isRtl: Boolean): Matrix {
-        return runBlocking {
-            when (
-                val thumbnailPositionState =
-                    getThumbnailPositionUseCase.run(taskId, width, height, isRtl)
-            ) {
-                is ThumbnailPositionState.MatrixScaling -> thumbnailPositionState.matrix
-                is ThumbnailPositionState.MissingThumbnail -> Matrix.IDENTITY_MATRIX
-            }
+    override fun getThumbnailPositionState(width: Int, height: Int, isRtl: Boolean): Matrix =
+        when (
+            val thumbnailPositionState =
+                getThumbnailPositionUseCase.run(taskId, width, height, isRtl)
+        ) {
+            is ThumbnailPositionState.MatrixScaling -> thumbnailPositionState.matrix
+            is ThumbnailPositionState.MissingThumbnail -> Matrix.IDENTITY_MATRIX
         }
-    }
 
     private fun isBackgroundOnly(task: Task): Boolean = task.isLocked || task.thumbnail == null
 
diff --git a/quickstep/src/com/android/quickstep/util/AppPairsController.java b/quickstep/src/com/android/quickstep/util/AppPairsController.java
index 1312aa4..8399792 100644
--- a/quickstep/src/com/android/quickstep/util/AppPairsController.java
+++ b/quickstep/src/com/android/quickstep/util/AppPairsController.java
@@ -28,8 +28,7 @@
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_NONE;
-import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
-import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.getIndex;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.isPersistentSnapPosition;
 
 import android.content.Context;
@@ -55,6 +54,7 @@
 import com.android.launcher3.model.data.AppPairInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.ItemInfoWithIcon;
+import com.android.launcher3.model.data.TaskViewItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.taskbar.TaskbarActivityContext;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
@@ -74,6 +74,7 @@
 
 import java.util.Arrays;
 import java.util.List;
+import java.util.Objects;
 import java.util.function.Consumer;
 
 /**
@@ -171,20 +172,6 @@
      */
     public void saveAppPair(GroupedTaskView gtv) {
         InteractionJankMonitorWrapper.begin(gtv, Cuj.CUJ_LAUNCHER_SAVE_APP_PAIR);
-        List<TaskContainer> containers = gtv.getTaskContainers();
-        WorkspaceItemInfo recentsInfo1 = containers.get(0).getItemInfo();
-        WorkspaceItemInfo recentsInfo2 = containers.get(1).getItemInfo();
-        WorkspaceItemInfo app1 = resolveAppPairWorkspaceInfo(recentsInfo1);
-        WorkspaceItemInfo app2 = resolveAppPairWorkspaceInfo(recentsInfo2);
-
-        if (app1 == null || app2 == null) {
-            // This shouldn't happen if canSaveAppPair() is called above, but log an error and do
-            // not create the app pair if the workspace items can't be resolved
-            Log.w(TAG, "Failed to save app pair due to invalid apps ("
-                    + "app1=" + recentsInfo1.getComponentKey().componentName
-                    + " app2=" + recentsInfo2.getComponentKey().componentName + ")");
-            return;
-        }
 
         @PersistentSnapPosition int snapPosition = gtv.getSnapPosition();
         if (snapPosition == SNAP_TO_NONE) {
@@ -198,16 +185,37 @@
             return;
         }
 
-        app1.rank = encodeRank(SPLIT_POSITION_TOP_OR_LEFT, snapPosition);
-        app2.rank = encodeRank(SPLIT_POSITION_BOTTOM_OR_RIGHT, snapPosition);
-        AppPairInfo newAppPair = new AppPairInfo(app1, app2);
+        List<TaskContainer> containers = gtv.getTaskContainers();
+        List<TaskViewItemInfo> recentsInfos =
+                containers.stream().map(TaskContainer::getItemInfo).toList();
+        List<WorkspaceItemInfo> apps =
+                recentsInfos.stream().map(this::resolveAppPairWorkspaceInfo).toList();
+
+        if (apps.stream().anyMatch(Objects::isNull)) {
+            // This shouldn't happen if canSaveAppPair() is called above, but log an error and do
+            // not create the app pair if the workspace items can't be resolved
+            StringBuilder error =
+                    new StringBuilder("Failed to save app pair due to invalid apps (");
+            for (int i = 0; i < recentsInfos.size(); i++) {
+                error.append("app").append(i).append("=")
+                        .append(recentsInfos.get(i).getComponentKey().componentName).append(" ");
+            }
+            error.append(")");
+            Log.w(TAG, error.toString());
+            return;
+        }
+
+        for (int i = 0; i < apps.size(); i++) {
+            apps.get(i).rank = encodeRank(getIndex(i), snapPosition);
+        }
+        AppPairInfo newAppPair = new AppPairInfo(apps);
 
         IconCache iconCache = LauncherAppState.getInstance(mContext).getIconCache();
         MODEL_EXECUTOR.execute(() -> {
             newAppPair.getAppContents().forEach(member -> {
                 member.title = "";
                 member.bitmap = iconCache.getDefaultIcon(newAppPair.user);
-                iconCache.getTitleAndIcon(member, member.usingLowResIcon());
+                iconCache.getTitleAndIcon(member, member.getMatchingLookupFlag());
             });
             MAIN_EXECUTOR.execute(() -> {
                 LauncherAccessibilityDelegate delegate =
diff --git a/quickstep/src/com/android/quickstep/util/BackAnimState.kt b/quickstep/src/com/android/quickstep/util/BackAnimState.kt
index 9009eaa..4c1e1ff 100644
--- a/quickstep/src/com/android/quickstep/util/BackAnimState.kt
+++ b/quickstep/src/com/android/quickstep/util/BackAnimState.kt
@@ -18,6 +18,7 @@
 
 import android.animation.AnimatorSet
 import android.content.Context
+import com.android.launcher3.Flags
 import com.android.launcher3.LauncherAnimationRunner.AnimationResult
 import com.android.launcher3.anim.AnimatorListeners.forEndCallback
 import com.android.launcher3.util.RunnableList
@@ -36,14 +37,20 @@
     BackAnimState {
 
     override fun addOnAnimCompleteCallback(r: Runnable) {
-        val springAnimWait = RunnableList()
-        springAnim?.addAnimatorListener(forEndCallback(springAnimWait::executeAllAndDestroy))
-            ?: springAnimWait.executeAllAndDestroy()
-
         val animWait = RunnableList()
-        anim?.addListener(
-            forEndCallback(Runnable { springAnimWait.add(animWait::executeAllAndDestroy) })
-        ) ?: springAnimWait.add(animWait::executeAllAndDestroy)
+        if (Flags.predictiveBackToHomePolish()) {
+            springAnim?.addAnimatorListener(forEndCallback(animWait::executeAllAndDestroy))
+                ?: anim?.addListener(forEndCallback(animWait::executeAllAndDestroy))
+                ?: animWait.executeAllAndDestroy()
+        } else {
+            val springAnimWait = RunnableList()
+            springAnim?.addAnimatorListener(forEndCallback(springAnimWait::executeAllAndDestroy))
+                ?: springAnimWait.executeAllAndDestroy()
+
+            anim?.addListener(
+                forEndCallback(Runnable { springAnimWait.add(animWait::executeAllAndDestroy) })
+            ) ?: springAnimWait.add(animWait::executeAllAndDestroy)
+        }
         animWait.add(r)
     }
 
diff --git a/quickstep/src/com/android/quickstep/util/RecentsViewUtils.kt b/quickstep/src/com/android/quickstep/util/RecentsViewUtils.kt
deleted file mode 100644
index 908e9f9..0000000
--- a/quickstep/src/com/android/quickstep/util/RecentsViewUtils.kt
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quickstep.util
-
-import com.android.launcher3.Flags.enableLargeDesktopWindowingTile
-import com.android.quickstep.RecentsAnimationController
-import com.android.quickstep.views.DesktopTaskView
-import com.android.quickstep.views.TaskView
-import com.android.quickstep.views.TaskViewType
-import com.android.systemui.shared.recents.model.ThumbnailData
-
-/**
- * Helper class for [com.android.quickstep.views.RecentsView]. This util class contains refactored
- * and extracted functions from RecentsView to facilitate the implementation of unit tests.
- */
-class RecentsViewUtils {
-    /** Takes a screenshot of all [taskView] and return map of taskId to the screenshot */
-    fun screenshotTasks(
-        taskView: TaskView,
-        recentsAnimationController: RecentsAnimationController,
-    ): Map<Int, ThumbnailData> =
-        taskView.taskContainers.associate {
-            it.task.key.id to recentsAnimationController.screenshotTask(it.task.key.id)
-        }
-
-    /**
-     * Sorts task groups to move desktop tasks to the end of the list.
-     *
-     * @param tasks List of group tasks to be sorted.
-     * @return Sorted list of GroupTasks to be used in the RecentsView.
-     */
-    fun sortDesktopTasksToFront(tasks: List<GroupTask>): List<GroupTask> {
-        val (desktopTasks, otherTasks) = tasks.partition { it.taskViewType == TaskViewType.DESKTOP }
-        return otherTasks + desktopTasks
-    }
-
-    /** Counts [TaskView]s that are [DesktopTaskView] instances. */
-    fun getDesktopTaskViewCount(taskViews: Iterable<TaskView>): Int =
-        taskViews.count { it is DesktopTaskView }
-
-    /** Returns a list of all large TaskView Ids from [TaskView]s */
-    fun getLargeTaskViewIds(taskViews: Iterable<TaskView>): List<Int> =
-        taskViews.filter { it.isLargeTile }.map { it.taskViewId }
-
-    /** Counts [TaskView]s that are large tiles. */
-    fun getLargeTileCount(taskViews: Iterable<TaskView>): Int = taskViews.count { it.isLargeTile }
-
-    /**
-     * Returns the first TaskView that should be displayed as a large tile.
-     *
-     * @param taskViews List of [TaskView]s
-     * @param splitSelectActive current split state
-     */
-    fun getFirstLargeTaskView(
-        taskViews: MutableIterable<TaskView>,
-        splitSelectActive: Boolean,
-    ): TaskView? =
-        taskViews.firstOrNull { it.isLargeTile && !(splitSelectActive && it is DesktopTaskView) }
-
-    /** Returns the expected focus task. */
-    fun getExpectedFocusedTask(taskViews: Iterable<TaskView>): TaskView? =
-        if (enableLargeDesktopWindowingTile()) taskViews.firstOrNull { it !is DesktopTaskView }
-        else taskViews.firstOrNull()
-
-    /**
-     * Returns the [TaskView] that should be the current page during task binding, in the following
-     * priorities:
-     * 1. Running task
-     * 2. Focused task
-     * 3. First non-desktop task
-     * 4. Last desktop task
-     * 5. null otherwise
-     */
-    fun getExpectedCurrentTask(
-        runningTaskView: TaskView?,
-        focusedTaskView: TaskView?,
-        taskViews: Iterable<TaskView>,
-    ): TaskView? =
-        runningTaskView
-            ?: focusedTaskView
-            ?: taskViews.firstOrNull { it !is DesktopTaskView }
-            ?: taskViews.lastOrNull()
-
-    /**
-     * Returns the first TaskView that is not large
-     *
-     * @param taskViews List of [TaskView]s
-     */
-    fun getFirstSmallTaskView(taskViews: MutableIterable<TaskView>): TaskView? =
-        taskViews.firstOrNull { !it.isLargeTile }
-
-    /** Returns the last TaskView that should be displayed as a large tile. */
-    fun getLastLargeTaskView(taskViews: Iterable<TaskView>): TaskView? =
-        taskViews.lastOrNull { it.isLargeTile }
-
-    /** Returns the first [TaskView], with some tasks possibly hidden in the carousel. */
-    fun getFirstTaskViewInCarousel(
-        nonRunningTaskCarouselHidden: Boolean,
-        taskViews: Iterable<TaskView>,
-        runningTaskView: TaskView?,
-    ): TaskView? =
-        taskViews.firstOrNull {
-            it.isVisibleInCarousel(runningTaskView, nonRunningTaskCarouselHidden)
-        }
-
-    /** Returns the last [TaskView], with some tasks possibly hidden in the carousel. */
-    fun getLastTaskViewInCarousel(
-        nonRunningTaskCarouselHidden: Boolean,
-        taskViews: Iterable<TaskView>,
-        runningTaskView: TaskView?,
-    ): TaskView? =
-        taskViews.lastOrNull {
-            it.isVisibleInCarousel(runningTaskView, nonRunningTaskCarouselHidden)
-        }
-
-    /** Returns if any small tasks are fully visible */
-    fun isAnySmallTaskFullyVisible(
-        taskViews: Iterable<TaskView>,
-        isTaskViewFullyVisible: (TaskView) -> Boolean,
-    ): Boolean = taskViews.any { !it.isLargeTile && isTaskViewFullyVisible(it) }
-
-    /** Returns the current list of [TaskView] children. */
-    fun getTaskViews(taskViewCount: Int, requireTaskViewAt: (Int) -> TaskView): Iterable<TaskView> =
-        (0 until taskViewCount).map(requireTaskViewAt)
-
-    /** Apply attachAlpha to all [TaskView] accordingly to different conditions. */
-    fun applyAttachAlpha(
-        taskViews: Iterable<TaskView>,
-        runningTaskView: TaskView?,
-        runningTaskAttachAlpha: Float,
-        nonRunningTaskCarouselHidden: Boolean,
-    ) {
-        taskViews.forEach { taskView ->
-            taskView.attachAlpha =
-                if (taskView == runningTaskView) {
-                    runningTaskAttachAlpha
-                } else {
-                    if (taskView.isVisibleInCarousel(runningTaskView, nonRunningTaskCarouselHidden))
-                        1f
-                    else 0f
-                }
-        }
-    }
-
-    fun TaskView.isVisibleInCarousel(
-        runningTaskView: TaskView?,
-        nonRunningTaskCarouselHidden: Boolean,
-    ): Boolean =
-        if (!nonRunningTaskCarouselHidden) true
-        else getCarouselType() == runningTaskView.getCarouselType()
-
-    /** Returns the carousel type of the TaskView, and default to fullscreen if it's null. */
-    private fun TaskView?.getCarouselType(): TaskViewCarousel =
-        if (this is DesktopTaskView) TaskViewCarousel.DESKTOP else TaskViewCarousel.FULL_SCREEN
-
-    private enum class TaskViewCarousel {
-        FULL_SCREEN,
-        DESKTOP,
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/util/ScalingWorkspaceRevealAnim.kt b/quickstep/src/com/android/quickstep/util/ScalingWorkspaceRevealAnim.kt
index f719bed..63eae92 100644
--- a/quickstep/src/com/android/quickstep/util/ScalingWorkspaceRevealAnim.kt
+++ b/quickstep/src/com/android/quickstep/util/ScalingWorkspaceRevealAnim.kt
@@ -54,14 +54,15 @@
     private val launcher: QuickstepLauncher,
     siblingAnimation: RectFSpringAnim?,
     windowTargetRect: RectF?,
+    playAlphaReveal: Boolean = true,
 ) {
     companion object {
         private const val FADE_DURATION_MS = 200L
         private const val SCALE_DURATION_MS = 1000L
         private const val MAX_ALPHA = 1f
         private const val MIN_ALPHA = 0f
-        private const val MAX_SIZE = 1f
-        private const val MIN_SIZE = 0.85f
+        internal const val MAX_SIZE = 1f
+        internal const val MIN_SIZE = 0.85f
 
         /**
          * Custom interpolator for both the home and wallpaper scaling. Necessary because EMPHASIZED
@@ -132,21 +133,23 @@
             SCALE_INTERPOLATOR,
         )
 
-        // Fade in quickly at the beginning of the animation, so the content doesn't look like it's
-        // popping into existence out of nowhere.
-        val fadeClamp = FADE_DURATION_MS.toFloat() / SCALE_DURATION_MS
-        workspace.alpha = MIN_ALPHA
-        animation.setViewAlpha(
-            workspace,
-            MAX_ALPHA,
-            Interpolators.clampToProgress(LINEAR, 0f, fadeClamp),
-        )
-        hotseat.alpha = MIN_ALPHA
-        animation.setViewAlpha(
-            hotseat,
-            MAX_ALPHA,
-            Interpolators.clampToProgress(LINEAR, 0f, fadeClamp),
-        )
+        if (playAlphaReveal) {
+            // Fade in quickly at the beginning of the animation, so the content doesn't look like
+            // it's popping into existence out of nowhere.
+            val fadeClamp = FADE_DURATION_MS.toFloat() / SCALE_DURATION_MS
+            workspace.alpha = MIN_ALPHA
+            animation.setViewAlpha(
+                workspace,
+                MAX_ALPHA,
+                Interpolators.clampToProgress(LINEAR, 0f, fadeClamp),
+            )
+            hotseat.alpha = MIN_ALPHA
+            animation.setViewAlpha(
+                hotseat,
+                MAX_ALPHA,
+                Interpolators.clampToProgress(LINEAR, 0f, fadeClamp),
+            )
+        }
 
         val transitionConfig = StateAnimationConfig()
 
diff --git a/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java b/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
index bdfaa48..86090d5 100644
--- a/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
@@ -16,6 +16,7 @@
 
 package com.android.quickstep.util;
 
+import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
 import android.animation.Animator;
@@ -89,7 +90,7 @@
         MODEL_EXECUTOR.execute(() -> {
             PackageItemInfo infoInOut = new PackageItemInfo(pendingIntent.getCreatorPackage(),
                     pendingIntent.getCreatorUserHandle());
-            mIconCache.getTitleAndIconForApp(infoInOut, false);
+            mIconCache.getTitleAndIconForApp(infoInOut, DEFAULT_LOOKUP_FLAG);
             Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
 
             view.post(() -> {
diff --git a/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java b/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java
index f3b984b8..7fadc7d 100644
--- a/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java
+++ b/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java
@@ -27,7 +27,10 @@
 import android.view.WindowMetrics;
 
 import com.android.internal.policy.SystemBarUtils;
+import com.android.launcher3.dagger.ApplicationContext;
+import com.android.launcher3.dagger.LauncherAppSingleton;
 import com.android.launcher3.statehandlers.DesktopVisibilityController;
+import com.android.launcher3.util.DaggerSingletonTracker;
 import com.android.launcher3.util.WindowBounds;
 import com.android.launcher3.util.window.CachedDisplayInfo;
 import com.android.launcher3.util.window.WindowManagerProxy;
@@ -37,22 +40,22 @@
 import java.util.List;
 import java.util.Set;
 
+import javax.inject.Inject;
+
 /**
  * Extension of {@link WindowManagerProxy} with some assumption for the default system Launcher
  */
+@LauncherAppSingleton
 public class SystemWindowManagerProxy extends WindowManagerProxy {
 
     private final TISBindHelper mTISBindHelper;
 
-    public SystemWindowManagerProxy(Context context) {
+    @Inject
+    public SystemWindowManagerProxy(@ApplicationContext Context context,
+            DaggerSingletonTracker lifecycleTracker) {
         super(true);
         mTISBindHelper = new TISBindHelper(context, binder -> {});
-    }
-
-    @Override
-    public void close() {
-        super.close();
-        mTISBindHelper.onDestroy();
+        lifecycleTracker.addCloseable(mTISBindHelper::onDestroy);
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/util/TISBindHelper.java b/quickstep/src/com/android/quickstep/util/TISBindHelper.java
index b573604..b238dec 100644
--- a/quickstep/src/com/android/quickstep/util/TISBindHelper.java
+++ b/quickstep/src/com/android/quickstep/util/TISBindHelper.java
@@ -21,6 +21,7 @@
 import android.content.ServiceConnection;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
 import android.util.Log;
 
 import androidx.annotation.Nullable;
@@ -46,7 +47,7 @@
     // Max backoff caps at 5 mins
     private static final long MAX_BACKOFF_MILLIS = 10 * 60 * 1000;
 
-    private final Handler mHandler = new Handler();
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
     private final Runnable mConnectionRunnable = this::internalBindToTIS;
     private final Context mContext;
     private final Consumer<TISBinder> mConnectionCallback;
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
index 576a56e..7d04451 100644
--- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
@@ -24,8 +24,10 @@
 import android.util.Log
 import android.view.Gravity
 import android.view.View
+import android.view.ViewStub
 import androidx.core.content.res.ResourcesCompat
 import androidx.core.view.updateLayoutParams
+import com.android.launcher3.Flags.enableOverviewIconMenu
 import com.android.launcher3.Flags.enableRefactorTaskThumbnail
 import com.android.launcher3.R
 import com.android.launcher3.testing.TestLogging
@@ -85,7 +87,7 @@
     override fun onFinishInflate() {
         super.onFinishInflate()
         iconView =
-            getOrInflateIconView(R.id.icon).apply {
+            (findViewById<View>(R.id.icon) as TaskViewIcon).apply {
                 setIcon(
                     this,
                     ResourcesCompat.getDrawable(
@@ -109,6 +111,16 @@
             }
     }
 
+    override fun inflateViewStubs() {
+        findViewById<ViewStub>(R.id.icon)
+            ?.apply {
+                layoutResource =
+                    if (enableOverviewIconMenu()) R.layout.icon_app_chip_view
+                    else R.layout.icon_view
+            }
+            ?.inflate()
+    }
+
     /** Updates this desktop task to the gives task list defined in `tasks` */
     fun bind(
         tasks: List<Task>,
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
index 0d9583d..3f0b520 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
@@ -21,8 +21,10 @@
 import android.util.AttributeSet
 import android.util.Log
 import android.view.View
+import android.view.ViewStub
 import com.android.internal.jank.Cuj
 import com.android.launcher3.Flags.enableOverviewIconMenu
+import com.android.launcher3.Flags.enableRefactorTaskThumbnail
 import com.android.launcher3.R
 import com.android.launcher3.Utilities
 import com.android.launcher3.config.FeatureFlags
@@ -36,6 +38,7 @@
 import com.android.quickstep.util.SplitSelectStateController
 import com.android.systemui.shared.recents.model.Task
 import com.android.systemui.shared.system.InteractionJankMonitorWrapper
+import com.android.wm.shell.Flags.enableFlexibleTwoAppSplit
 import com.android.wm.shell.shared.split.SplitScreenConstants.PersistentSnapPosition
 
 /**
@@ -50,6 +53,9 @@
  */
 class GroupedTaskView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
     TaskView(context, attrs, type = TaskViewType.GROUPED) {
+
+    private val MINIMUM_RATIO_TO_SHOW_ICON = 0.2f
+
     // TODO(b/336612373): Support new TTV for GroupedTaskView
     var splitBoundsConfig: SplitConfigurationOptions.SplitBounds? = null
         private set
@@ -82,6 +88,24 @@
         }
     }
 
+    override fun inflateViewStubs() {
+        super.inflateViewStubs()
+        findViewById<ViewStub>(R.id.bottomright_snapshot)
+            ?.apply {
+                layoutResource =
+                    if (enableRefactorTaskThumbnail()) R.layout.task_thumbnail
+                    else R.layout.task_thumbnail_deprecated
+            }
+            ?.inflate()
+        findViewById<ViewStub>(R.id.bottomRight_icon)
+            ?.apply {
+                layoutResource =
+                    if (enableOverviewIconMenu()) R.layout.icon_app_chip_view
+                    else R.layout.icon_view
+            }
+            ?.inflate()
+    }
+
     override fun onRecycle() {
         super.onRecycle()
         splitBoundsConfig = null
@@ -156,14 +180,32 @@
 
     private fun updateIconPlacement() {
         val splitBoundsConfig = splitBoundsConfig ?: return
-        val taskIconHeight = container.deviceProfile.overviewTaskIconSizePx
+        val deviceProfile = container.deviceProfile
+        val taskIconHeight = deviceProfile.overviewTaskIconSizePx
         val isRtl = layoutDirection == LAYOUT_DIRECTION_RTL
         val inSplitSelection = getThisTaskCurrentlyInSplitSelection() != INVALID_TASK_ID
 
+        if (enableFlexibleTwoAppSplit()) {
+            val topLeftTaskPercent =
+                if (deviceProfile.isLeftRightSplit) splitBoundsConfig.leftTaskPercent
+                else splitBoundsConfig.topTaskPercent
+            val bottomRightTaskPercent = 1 - topLeftTaskPercent
+            taskContainers[0]
+                .iconView
+                .setFlexSplitAlpha(
+                    if (topLeftTaskPercent < MINIMUM_RATIO_TO_SHOW_ICON) 0f else 1f
+                )
+            taskContainers[1]
+                .iconView
+                .setFlexSplitAlpha(
+                    if (bottomRightTaskPercent < MINIMUM_RATIO_TO_SHOW_ICON) 0f else 1f
+                )
+        }
+
         if (enableOverviewIconMenu()) {
             val groupedTaskViewSizes =
                 pagedOrientationHandler.getGroupedTaskViewSizes(
-                    container.deviceProfile,
+                    deviceProfile,
                     splitBoundsConfig,
                     layoutParams.width,
                     layoutParams.height,
@@ -177,7 +219,7 @@
                 layoutParams.height,
                 layoutParams.width,
                 isRtl,
-                container.deviceProfile,
+                deviceProfile,
                 splitBoundsConfig,
                 inSplitSelection,
             )
@@ -191,7 +233,7 @@
                 measuredHeight,
                 measuredWidth,
                 isRtl,
-                container.deviceProfile,
+                deviceProfile,
                 splitBoundsConfig,
                 inSplitSelection,
             )
diff --git a/quickstep/src/com/android/quickstep/views/IconAppChipView.java b/quickstep/src/com/android/quickstep/views/IconAppChipView.java
index ba42594..5270477 100644
--- a/quickstep/src/com/android/quickstep/views/IconAppChipView.java
+++ b/quickstep/src/com/android/quickstep/views/IconAppChipView.java
@@ -56,10 +56,12 @@
     private static final int MENU_BACKGROUND_REVEAL_DURATION = 417;
     private static final int MENU_BACKGROUND_HIDE_DURATION = 333;
 
-    private static final int NUM_ALPHA_CHANNELS = 3;
+    private static final int NUM_ALPHA_CHANNELS = 4;
     private static final int INDEX_CONTENT_ALPHA = 0;
     private static final int INDEX_COLOR_FILTER_ALPHA = 1;
     private static final int INDEX_MODAL_ALPHA = 2;
+    /** Used to hide the app chip for 90:10 flex split. */
+    private static final int INDEX_MINIMUM_RATIO_ALPHA = 3;
 
     private final MultiValueAlpha mMultiValueAlpha;
 
@@ -349,6 +351,11 @@
     }
 
     @Override
+    public void setFlexSplitAlpha(float alpha) {
+        mMultiValueAlpha.get(INDEX_MINIMUM_RATIO_ALPHA).setValue(alpha);
+    }
+
+    @Override
     public int getDrawableWidth() {
         return mIconView == null ? 0 : mIconView.getDrawableWidth();
     }
diff --git a/quickstep/src/com/android/quickstep/views/IconView.kt b/quickstep/src/com/android/quickstep/views/IconView.kt
index 583207f..2e6c4bf 100644
--- a/quickstep/src/com/android/quickstep/views/IconView.kt
+++ b/quickstep/src/com/android/quickstep/views/IconView.kt
@@ -25,10 +25,13 @@
 import android.widget.FrameLayout
 import androidx.core.view.updateLayoutParams
 import com.android.launcher3.DeviceProfile
+import com.android.launcher3.Flags
 import com.android.launcher3.Utilities
+import com.android.launcher3.util.MSDLPlayerWrapper
 import com.android.launcher3.util.MultiValueAlpha
 import com.android.launcher3.views.ActivityContext
 import com.android.quickstep.util.RecentsOrientedState
+import com.google.android.msdl.data.model.MSDLToken
 
 /**
  * A view which draws a drawable stretched to fit its size. Unlike ImageView, it avoids relayout
@@ -39,19 +42,32 @@
     private var drawable: Drawable? = null
     private var drawableWidth = 0
     private var drawableHeight = 0
+    private var msdlPlayerWrapper: MSDLPlayerWrapper? = null
 
-    constructor(context: Context) : super(context)
+    constructor(context: Context) : super(context) {
+        msdlPlayerWrapper = MSDLPlayerWrapper.INSTANCE.get(context)
+    }
 
-    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
+    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
+        msdlPlayerWrapper = MSDLPlayerWrapper.INSTANCE.get(context)
+    }
 
     constructor(
         context: Context,
         attrs: AttributeSet?,
         defStyleAttr: Int,
-    ) : super(context, attrs, defStyleAttr)
+    ) : super(context, attrs, defStyleAttr) {
+        msdlPlayerWrapper = MSDLPlayerWrapper.INSTANCE.get(context)
+    }
 
     init {
         multiValueAlpha.setUpdateVisibility(true)
+        // Haptics are handled by the MSDLPlayerWrapper
+        isHapticFeedbackEnabled = !Flags.msdlFeedback() || msdlPlayerWrapper == null
+    }
+
+    override fun setOnLongClickListener(l: OnLongClickListener?) {
+        super.setOnLongClickListener(l?.withFeedback())
     }
 
     /** Sets a [Drawable] to be displayed. */
@@ -117,6 +133,10 @@
         multiValueAlpha[INDEX_MODAL_ALPHA].setValue(alpha)
     }
 
+    override fun setFlexSplitAlpha(alpha: Float) {
+        multiValueAlpha[INDEX_FLEX_SPLIT_ALPHA].setValue(alpha)
+    }
+
     /**
      * Set the tint color of the icon, useful for scrimming or dimming.
      *
@@ -136,7 +156,7 @@
             taskIconMargin = deviceProfile.overviewTaskMarginPx,
             taskIconHeight = deviceProfile.overviewTaskIconSizePx,
             thumbnailTopMargin = deviceProfile.overviewTaskThumbnailTopMarginPx,
-            isRtl = layoutDirection == LAYOUT_DIRECTION_RTL
+            isRtl = layoutDirection == LAYOUT_DIRECTION_RTL,
         )
         updateLayoutParams<FrameLayout.LayoutParams> {
             height = deviceProfile.overviewTaskIconSizePx
@@ -151,9 +171,20 @@
 
     override fun asView(): View = this
 
+    private fun OnLongClickListener.withFeedback(): OnLongClickListener {
+        val delegate = this
+        return OnLongClickListener { v: View ->
+            if (Flags.msdlFeedback()) {
+                msdlPlayerWrapper?.playToken(MSDLToken.LONG_PRESS)
+            }
+            delegate.onLongClick(v)
+        }
+    }
+
     companion object {
-        private const val NUM_ALPHA_CHANNELS = 2
+        private const val NUM_ALPHA_CHANNELS = 3
         private const val INDEX_CONTENT_ALPHA = 0
         private const val INDEX_MODAL_ALPHA = 1
+        private const val INDEX_FLEX_SPLIT_ALPHA = 2
     }
 }
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index bbb8cc8..f983e38 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -47,6 +47,7 @@
 import com.android.launcher3.util.PendingSplitSelectInfo;
 import com.android.launcher3.util.SplitConfigurationOptions;
 import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource;
+import com.android.quickstep.BaseContainerInterface;
 import com.android.quickstep.GestureState;
 import com.android.quickstep.LauncherActivityInterface;
 import com.android.quickstep.RotationTouchHelper;
@@ -73,7 +74,7 @@
     }
 
     public LauncherRecentsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr, LauncherActivityInterface.INSTANCE);
+        super(context, attrs, defStyleAttr);
         getStateManager().addStateListener(this);
     }
 
@@ -163,13 +164,14 @@
         }
 
         setFreezeViewVisibility(true);
-        if (mContainer.getDesktopVisibilityController() != null) {
-            mContainer.getDesktopVisibilityController().onLauncherStateChanged(toState);
-        }
     }
 
     @Override
     public void onStateTransitionComplete(LauncherState finalState) {
+        if (mContainer.getDesktopVisibilityController() != null) {
+            mContainer.getDesktopVisibilityController().onLauncherStateChanged(finalState);
+        }
+
         if (!finalState.isRecentsViewVisible) {
             // Clean-up logic that occurs when recents is no longer in use/visible.
             reset();
@@ -225,6 +227,11 @@
     }
 
     @Override
+    protected BaseContainerInterface<LauncherState, ?> getContainerInterface(int displayId) {
+        return LauncherActivityInterface.INSTANCE;
+    }
+
+    @Override
     protected void onDismissAnimationEnds() {
         super.onDismissAnimationEnds();
         if (mContainer.isInState(OVERVIEW_SPLIT_SELECT)) {
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index e59d2ff..f9f3487 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -25,11 +25,11 @@
 import static com.android.app.animation.Interpolators.ACCELERATE_0_75;
 import static com.android.app.animation.Interpolators.ACCELERATE_DECELERATE;
 import static com.android.app.animation.Interpolators.DECELERATE_2;
+import static com.android.app.animation.Interpolators.EMPHASIZED;
 import static com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE;
 import static com.android.app.animation.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.app.animation.Interpolators.FINAL_FRAME;
 import static com.android.app.animation.Interpolators.LINEAR;
-import static com.android.app.animation.Interpolators.EMPHASIZED;
 import static com.android.app.animation.Interpolators.clampToProgress;
 import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE;
 import static com.android.launcher3.AbstractFloatingView.TYPE_TASK_MENU;
@@ -179,6 +179,7 @@
 import com.android.launcher3.util.TranslateEdgeEffect;
 import com.android.launcher3.util.VibratorWrapper;
 import com.android.launcher3.util.ViewPool;
+import com.android.launcher3.util.coroutines.DispatcherProvider;
 import com.android.quickstep.BaseContainerInterface;
 import com.android.quickstep.GestureState;
 import com.android.quickstep.OverviewCommandHelper;
@@ -213,7 +214,6 @@
 import com.android.quickstep.util.LayoutUtils;
 import com.android.quickstep.util.RecentsAtomicAnimationFactory;
 import com.android.quickstep.util.RecentsOrientedState;
-import com.android.quickstep.util.RecentsViewUtils;
 import com.android.quickstep.util.SplitAnimationController.Companion.SplitAnimInitProps;
 import com.android.quickstep.util.SplitAnimationTimings;
 import com.android.quickstep.util.SplitSelectStateController;
@@ -237,12 +237,16 @@
 import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
 
 import kotlin.Unit;
+import kotlin.collections.CollectionsKt;
+
+import kotlinx.coroutines.CoroutineScope;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -503,7 +507,7 @@
     private static final float FOREGROUND_SCRIM_TINT = 0.32f;
 
     protected final RecentsOrientedState mOrientationState;
-    protected final BaseContainerInterface<STATE_TYPE, CONTAINER_TYPE> mSizeStrategy;
+    protected final BaseContainerInterface<STATE_TYPE, ?> mSizeStrategy;
     @Nullable
     protected RecentsAnimationController mRecentsAnimationController;
     @Nullable
@@ -691,7 +695,7 @@
     protected boolean mRunningTaskTileHidden;
     protected int mFocusedTaskViewId = INVALID_TASK_ID;
 
-    private boolean mTaskIconScaledDown = false;
+    private boolean mTaskIconVisible = true;
     private boolean mRunningTaskShowScreenshot = false;
     private float mRunningTaskAttachAlpha;
 
@@ -842,17 +846,77 @@
 
     private final RecentsViewModel mRecentsViewModel;
     private final RecentsViewModelHelper mHelper;
-    private final RecentsViewUtils mUtils = new RecentsViewUtils();
+    private final RecentsViewUtils mUtils = new RecentsViewUtils(this);
 
     private final Matrix mTmpMatrix = new Matrix();
 
-    public RecentsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
-            BaseContainerInterface sizeStrategy) {
+    private int mTaskViewCount = 0;
+
+    private final TaskViewsIterable mTaskViewsIterable = new TaskViewsIterable();
+
+    public class TaskViewsIterable implements Iterable<TaskView> {
+        @Override
+        public TaskViewsIterator iterator() {
+            return new TaskViewsIterator();
+        }
+    }
+
+    // An Iterator to iterate all the current TaskViews inside the RecentsView.
+    public class TaskViewsIterator implements Iterator<TaskView> {
+        // Refers to the index of the `TaskView` that will be returned when `next()` is called.
+        private int mNextIndex = 0;
+
+        // The "limit" of this iterator. This is the number of children of the RecentsView when
+        // the iterator was created. Adding & removing elements will invalidate the iteration
+        // anyway (and cause next() to throw) so saving this value will guarantee that the
+        // value of hasNext() remains stable and won't flap between true and false when elements
+        // are added and removed from the RecentsView.
+        private final int mLimit = getChildCount();
+
+        TaskViewsIterator() {
+            advanceIfNeeded();
+        }
+
+        @Override
+        public boolean hasNext() {
+            return mNextIndex < mLimit && mNextIndex < getChildCount();
+        }
+
+        @Override
+        public TaskView next() {
+            if (!hasNext()) {
+                throw new IndexOutOfBoundsException(
+                        String.format("mNextIndex: %d, child count: %d", mNextIndex,
+                                getChildCount()));
+            }
+            TaskView taskView = requireTaskViewAt(mNextIndex);
+            mNextIndex++;
+            advanceIfNeeded();
+            return taskView;
+        }
+
+        // Advances `mNextIndex` until it either points to a `TaskView` or to the end of the
+        // Iterator.
+        private void advanceIfNeeded() {
+            while (mNextIndex < mLimit && mNextIndex < getChildCount() && !(getChildAt(
+                    mNextIndex) instanceof TaskView)) {
+                mNextIndex++;
+            }
+        }
+    }
+
+    @Nullable
+    public TaskView getFirstTaskView() {
+        return mUtils.getFirstTaskView();
+    }
+
+    public RecentsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
         setEnableFreeScroll(true);
 
-        mSizeStrategy = sizeStrategy;
         mContainer = RecentsViewContainer.containerFromContext(context);
+        mSizeStrategy = getContainerInterface(mContainer.getDisplayId());
+
         mOrientationState = new RecentsOrientedState(
                 context, mSizeStrategy, this::animateRecentsRotationInPlace);
         final int rotation = mContainer.getDisplay().getRotation();
@@ -866,7 +930,11 @@
                     recentsDependencies.inject(RecentTasksRepository.class),
                     recentsDependencies.inject(RecentsViewData.class)
             );
-            mHelper = new RecentsViewModelHelper(mRecentsViewModel);
+            mHelper = new RecentsViewModelHelper(
+                    mRecentsViewModel,
+                    recentsDependencies.inject(CoroutineScope.class),
+                    recentsDependencies.inject(DispatcherProvider.class)
+            );
 
             recentsDependencies.provide(RecentsRotationStateRepository.class,
                     () -> new RecentsRotationStateRepositoryImpl(mOrientationState));
@@ -1160,7 +1228,7 @@
     public void init(OverviewActionsView actionsView, SplitSelectStateController splitController,
             @Nullable DesktopRecentsTransitionController desktopRecentsTransitionController) {
         mActionsView = actionsView;
-        mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, getTaskViewCount() == 0);
+        mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, !hasTaskViews());
         // Update flags for 1p/3p launchers
         mActionsView.updateFor3pLauncher(!supportsAppPairs());
         mSplitSelectStateController = splitController;
@@ -1203,9 +1271,6 @@
         if (FeatureFlags.enableSplitContextually()) {
             mSplitSelectStateController.registerSplitListener(mSplitSelectionListener);
         }
-        if (enableRefactorTaskThumbnail()) {
-            mHelper.onAttachedToWindow();
-        }
     }
 
     @Override
@@ -1242,26 +1307,31 @@
         // - It's the initial taskview for entering split screen, we only pretend to dismiss the
         // task
         // - It's the focused task to be moved to the front, we immediately re-add the task
-        if (child instanceof TaskView && child != mSplitHiddenTaskView
-                && child != mMovingTaskView) {
-            TaskView taskView = (TaskView) child;
-            for (int i : taskView.getTaskIds()) {
-                mHasVisibleTaskData.delete(i);
+        if (child instanceof TaskView) {
+            mTaskViewCount = Math.max(0, --mTaskViewCount);
+            if (child != mSplitHiddenTaskView && child != mMovingTaskView) {
+                TaskView taskView = (TaskView) child;
+                for (int i : taskView.getTaskIds()) {
+                    mHasVisibleTaskData.delete(i);
+                }
+                if (child instanceof GroupedTaskView) {
+                    mGroupedTaskViewPool.recycle((GroupedTaskView) taskView);
+                } else if (child instanceof DesktopTaskView) {
+                    mDesktopTaskViewPool.recycle((DesktopTaskView) taskView);
+                } else {
+                    mTaskViewPool.recycle(taskView);
+                }
+                mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, !hasTaskViews());
             }
-            if (child instanceof GroupedTaskView) {
-                mGroupedTaskViewPool.recycle((GroupedTaskView) taskView);
-            } else if (child instanceof DesktopTaskView) {
-                mDesktopTaskViewPool.recycle((DesktopTaskView) taskView);
-            } else {
-                mTaskViewPool.recycle(taskView);
-            }
-            mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, getTaskViewCount() == 0);
         }
     }
 
     @Override
     public void onViewAdded(View child) {
         super.onViewAdded(child);
+        if (child instanceof TaskView) {
+            mTaskViewCount++;
+        }
         child.setAlpha(mContentAlpha);
         // RecentsView is set to RTL in the constructor when system is using LTR. Here we set the
         // child direction back to match system settings.
@@ -1493,30 +1563,16 @@
     }
 
     /**
-     * Returns true if the task is in expected scroll position.
-     *
-     * @param taskIndex the index of the task
+     * Returns true if the given TaskView is in expected scroll position.
      */
-    public boolean isTaskInExpectedScrollPosition(int taskIndex) {
-        return getScrollForPage(taskIndex) == getPagedOrientationHandler().getPrimaryScroll(this);
+    public boolean isTaskInExpectedScrollPosition(TaskView taskView) {
+        return getScrollForPage(indexOfChild(taskView))
+                == getPagedOrientationHandler().getPrimaryScroll(this);
     }
 
     private boolean isFocusedTaskInExpectedScrollPosition() {
         TaskView focusedTask = getFocusedTaskView();
-        return focusedTask != null && isTaskInExpectedScrollPosition(indexOfChild(focusedTask));
-    }
-
-    /**
-     * Launch DesktopTaskView if found.
-     * @return provides runnable list to attach runnable at end of Desktop Mode launch
-     */
-    public RunnableList launchDesktopTaskView() {
-        for (TaskView taskView : getTaskViews()) {
-            if (taskView instanceof DesktopTaskView) {
-                return taskView.launchWithAnimation();
-            }
-        }
-        return null;
+        return focusedTask != null && isTaskInExpectedScrollPosition(focusedTask);
     }
 
     /**
@@ -1731,8 +1787,7 @@
                 }
                 TaskView taskView = getTaskViewAt(mNextPage);
                 boolean shouldSnapToLargeTask = taskView != null && taskView.isLargeTile()
-                        && !mUtils.isAnySmallTaskFullyVisible(getTaskViews(),
-                        this::isTaskViewFullyVisible);
+                        && !mUtils.isAnySmallTaskFullyVisible();
                 boolean shouldSnapToClearAll = mNextPage == indexOfChild(mClearAllButton);
                 // Snap to large tile when grid tasks aren't fully visible or the clear all button.
                 if (!shouldSnapToLargeTask && !shouldSnapToClearAll) {
@@ -1976,7 +2031,7 @@
             }
             // If the list changed, maybe the focused task doesn't exist anymore.
             if (newFocusedTaskView == null) {
-                newFocusedTaskView = mUtils.getExpectedFocusedTask(getTaskViews());
+                newFocusedTaskView = mUtils.getExpectedFocusedTask();
             }
         }
         setFocusedTaskViewId(
@@ -2020,8 +2075,7 @@
             targetPage = previousFocusedPage;
         } else {
             targetPage = indexOfChild(
-                    mUtils.getExpectedCurrentTask(newRunningTaskView, newFocusedTaskView,
-                            getTaskViews()));
+                    mUtils.getExpectedCurrentTask(newRunningTaskView, newFocusedTaskView));
         }
         if (targetPage != -1 && mCurrentPage != targetPage) {
             int finalTargetPage = targetPage;
@@ -2063,25 +2117,23 @@
     }
 
     private void removeTasksViewsAndClearAllButton() {
-        for (TaskView taskView : getTaskViews()) {
-            if (isGestureActive() && taskView.isRunningTask()) {
-                // This handles an edge case where applyLoadPlan happens during a gesture when the
-                // only Task is one with excludeFromRecents, in which case we should not remove it.
-                continue;
-            }
-            removeView(taskView);
-        }
-        if (getTaskViewCount() == 0 && indexOfChild(mClearAllButton) != -1) {
+        // This handles an edge case where applyLoadPlan happens during a gesture when the only
+        // Task is one with excludeFromRecents, in which case we should not remove it.
+        CollectionsKt
+                .filter(getTaskViews(), taskView -> !isGestureActive() || !taskView.isRunningTask())
+                .forEach(this::removeView);
+        if (!hasTaskViews() && indexOfChild(mClearAllButton) != -1) {
             removeView(mClearAllButton);
         }
     }
 
+    /** Returns true if there are at least one TaskView has been added to the RecentsView. */
+    public boolean hasTaskViews() {
+        return CollectionsKt.any(getTaskViews());
+    }
+
     public int getTaskViewCount() {
-        int taskViewCount = getChildCount();
-        if (indexOfChild(mClearAllButton) != -1) {
-            taskViewCount--;
-        }
-        return taskViewCount;
+        return mTaskViewCount;
     }
 
     /**
@@ -2090,7 +2142,7 @@
      * @return Number of children that are instances of DesktopTaskView
      */
     private int getDesktopTaskViewCount() {
-        return mUtils.getDesktopTaskViewCount(getTaskViews());
+        return mUtils.getDesktopTaskViewCount();
     }
 
     /**
@@ -2117,7 +2169,7 @@
             if (Arrays.stream(taskView.getTaskIds()).noneMatch(
                     taskId -> taskId == mIgnoreResetTaskId)) {
                 taskView.resetViewTransforms();
-                taskView.setIconScaleAndDim(mTaskIconScaledDown ? 0 : 1);
+                taskView.setIconVisibleForGesture(mTaskIconVisible);
                 taskView.setStableAlpha(mContentAlpha);
                 taskView.setFullscreenProgress(mFullscreenProgress);
                 taskView.setModalness(mTaskModalness);
@@ -2287,8 +2339,7 @@
      * Updates TaskView scaling and translation required to support variable width.
      */
     private void updateTaskSize() {
-        final int taskCount = getTaskViewCount();
-        if (taskCount == 0) {
+        if (!hasTaskViews()) {
             return;
         }
 
@@ -2785,7 +2836,7 @@
         setEnableFreeScroll(false);
         setEnableDrawingLiveTile(false);
         setRunningTaskHidden(true);
-        setTaskIconScaledDown(true);
+        setTaskIconVisible(false);
     }
 
     /**
@@ -2804,7 +2855,7 @@
      * {@link #onGestureAnimationStart} and {@link #onGestureAnimationEnd()}.
      */
     public void onSwipeUpAnimationSuccess() {
-        animateUpTaskIconScale();
+        startIconFadeInOnGestureComplete();
         setSwipeDownShouldLaunchApp(true);
     }
 
@@ -2931,7 +2982,7 @@
         setEnableDrawingLiveTile(mCurrentGestureEndTarget == GestureState.GestureEndTarget.RECENTS);
         Log.d(TAG, "onGestureAnimationEnd - mEnableDrawingLiveTile: " + mEnableDrawingLiveTile);
         setRunningTaskHidden(false);
-        animateUpTaskIconScale();
+        startIconFadeInOnGestureComplete();
         animateActionsViewIn();
 
         mCurrentGestureEndTarget = null;
@@ -3055,7 +3106,7 @@
 
         if (mRunningTaskViewId != -1) {
             // Reset the state on the old running task view
-            setTaskIconScaledDown(false);
+            setTaskIconVisible(true);
             setRunningTaskViewShowScreenshot(true);
             setRunningTaskHidden(false);
         }
@@ -3104,7 +3155,7 @@
     private void applyAttachAlpha() {
         // Only hide non running task carousel when it's fully off screen, otherwise it needs to
         // be visible to move to on screen.
-        mUtils.applyAttachAlpha(getTaskViews(), getRunningTaskView(), mRunningTaskAttachAlpha,
+        mUtils.applyAttachAlpha(
                 /*nonRunningTaskCarouselHidden=*/mDesktopCarouselDetachProgress == 1f);
     }
 
@@ -3124,25 +3175,31 @@
         }
     }
 
-    public void setTaskIconScaledDown(boolean isScaledDown) {
-        if (mTaskIconScaledDown != isScaledDown) {
-            mTaskIconScaledDown = isScaledDown;
+    /**
+     * Updates icon visibility when going in or out of overview.
+     */
+    public void setTaskIconVisible(boolean isVisible) {
+        if (mTaskIconVisible != isVisible) {
+            mTaskIconVisible = isVisible;
             for (TaskView taskView : getTaskViews()) {
-                taskView.setIconScaleAndDim(mTaskIconScaledDown ? 0 : 1);
+                taskView.setIconVisibleForGesture(mTaskIconVisible);
             }
         }
     }
 
     private void animateActionsViewIn() {
         if (!showAsGrid() || isFocusedTaskInExpectedScrollPosition()) {
-            animateActionsViewAlpha(1, TaskView.SCALE_ICON_DURATION);
+            animateActionsViewAlpha(1, TaskView.FADE_IN_ICON_DURATION);
         }
     }
 
-    public void animateUpTaskIconScale() {
-        mTaskIconScaledDown = false;
+    /**
+     * Updates icon visibility when gesture is settled.
+     */
+    public void startIconFadeInOnGestureComplete() {
+        mTaskIconVisible = true;
         for (TaskView taskView : getTaskViews()) {
-            taskView.animateIconScaleAndDimIntoView();
+            taskView.startIconFadeInOnGestureComplete();
         }
     }
 
@@ -3167,8 +3224,7 @@
      *                            to skip rebalance
      */
     private void updateGridProperties(int startRebalanceAfter) {
-        int taskCount = getTaskViewCount();
-        if (taskCount == 0) {
+        if (!hasTaskViews()) {
             return;
         }
 
@@ -3185,10 +3241,11 @@
         IntSet topSet = new IntSet();
         IntSet bottomSet = new IntSet();
 
+        final int taskCount = getTaskViewCount();
         // Horizontal grid translation for each task
         float[] gridTranslations = new float[taskCount];
 
-        TaskView lastLargeTaskView = mUtils.getLastLargeTaskView(getTaskViews());
+        TaskView lastLargeTaskView = mUtils.getLastLargeTaskView();
         int lastLargeTaskIndex =
                 (lastLargeTaskView == null) ? Integer.MAX_VALUE : indexOfChild(lastLargeTaskView);
         Set<Integer> largeTasksIndices = new HashSet<>();
@@ -3726,7 +3783,7 @@
         boolean currentPageSnapsToEndOfGrid = currentPageScroll == lastGridTaskScroll;
 
         int topGridRowSize = mTopRowIdSet.size();
-        int numLargeTiles = mUtils.getLargeTileCount(getTaskViews());
+        int numLargeTiles = mUtils.getLargeTileCount();
         int bottomGridRowSize = taskCount - mTopRowIdSet.size() - numLargeTiles;
         boolean topRowLonger = topGridRowSize > bottomGridRowSize;
         boolean bottomRowLonger = bottomGridRowSize > topGridRowSize;
@@ -3857,14 +3914,15 @@
         int slidingTranslation = 0;
         if (isSlidingTasks) {
             int nextSnappedPage = isStagingFocusedTask
-                    ? indexOfChild(mUtils.getFirstSmallTaskView(getTaskViews()))
-                    : mUtils.getDesktopTaskViewCount(getTaskViews());
+                    ? indexOfChild(mUtils.getFirstSmallTaskView())
+                    : mUtils.getDesktopTaskViewCount();
             slidingTranslation = getPagedOrientationHandler().getPrimaryScroll(this)
                     - getScrollForPage(nextSnappedPage);
             slidingTranslation += mIsRtl ? newClearAllShortTotalWidthTranslation
                     : -newClearAllShortTotalWidthTranslation;
         }
         mDismissPrimaryTranslations = new int[taskCount];
+        int lastTaskViewIndex = indexOfChild(mUtils.getLastTaskView());
         for (int i = 0; i < count; i++) {
             View child = getChildAt(i);
             if (child == dismissedTaskView) {
@@ -3874,7 +3932,8 @@
             } else if (!showAsGrid || (enableLargeDesktopWindowingTile()
                     && dismissedTaskView != null && dismissedTaskView.isLargeTile()
                     && nextFocusedTaskView == null && !dismissingForSplitSelection)) {
-                int offset = getOffsetToDismissedTask(scrollDiffPerPage, dismissedIndex, taskCount);
+                int offset = getOffsetToDismissedTask(scrollDiffPerPage, dismissedIndex,
+                        lastTaskViewIndex);
                 int scrollDiff = newScroll[i] - oldScroll[i] + offset;
                 if (scrollDiff != 0) {
                     translateTaskWhenDismissed(
@@ -3894,25 +3953,40 @@
                         : distanceFromDismissedTask;
                 // Set timings based on if user is initiating splitscreen on the focused task,
                 // or splitting/dismissing some other task.
-                float animationStartProgress = isSlidingTasks
-                        ? Utilities.boundToRange(
-                        splitTimings.getGridSlideStartOffset()
-                                + (splitTimings.getGridSlideStaggerOffset()
-                                * staggerColumn),
-                        0f,
-                        dismissTranslationInterpolationEnd)
-                        : Utilities.boundToRange(
-                                INITIAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
-                                        + ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
-                                        * staggerColumn, 0f, dismissTranslationInterpolationEnd);
-                float animationEndProgress = isSlidingTasks
-                        ? Utilities.boundToRange(
-                        splitTimings.getGridSlideStartOffset()
-                                + (splitTimings.getGridSlideStaggerOffset() * staggerColumn)
-                                + splitTimings.getGridSlideDurationOffset(),
-                        0f,
-                        dismissTranslationInterpolationEnd)
-                        : dismissTranslationInterpolationEnd;
+                final float animationStartProgress;
+                if (isSlidingTasks) {
+                    float slidingStartOffset = splitTimings.getGridSlideStartOffset()
+                            + (splitTimings.getGridSlideStaggerOffset() * staggerColumn);
+                    if (areAllDesktopTasksDismissed) {
+                        animationStartProgress = Utilities.boundToRange(
+                                slidingStartOffset
+                                        + splitTimings.getDesktopFadeSplitAnimationEndOffset(),
+                                0f,
+                                dismissTranslationInterpolationEnd);
+                    } else {
+                        animationStartProgress = Utilities.boundToRange(
+                                slidingStartOffset,
+                                0f,
+                                dismissTranslationInterpolationEnd);
+                    }
+                } else {
+                    animationStartProgress = Utilities.boundToRange(
+                            INITIAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
+                                    + ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
+                                    * staggerColumn, 0f, dismissTranslationInterpolationEnd);
+                }
+
+                final float animationEndProgress;
+                if (isSlidingTasks && taskView != nextFocusedTaskView) {
+                    animationEndProgress = Utilities.boundToRange(
+                            splitTimings.getGridSlideStartOffset()
+                                    + (splitTimings.getGridSlideStaggerOffset() * staggerColumn)
+                                    + splitTimings.getGridSlideDurationOffset(),
+                            0f,
+                            dismissTranslationInterpolationEnd);
+                } else {
+                    animationEndProgress = dismissTranslationInterpolationEnd;
+                }
 
                 Interpolator dismissInterpolator = isSlidingTasks ? EMPHASIZED : LINEAR;
 
@@ -3924,7 +3998,6 @@
                             clampToProgress(LINEAR, animationStartProgress,
                                     dismissTranslationInterpolationEnd));
                     primaryTranslation += dismissedTaskWidth;
-                    animationEndProgress = dismissTranslationInterpolationEnd;
                     float secondaryTranslation = -mTaskGridVerticalDiff;
                     if (!nextFocusedTaskFromTop) {
                         secondaryTranslation -= mTopBottomRowHeightDiff;
@@ -3932,7 +4005,7 @@
                     anim.setFloat(taskView, taskView.getSecondaryDismissTranslationProperty(),
                             secondaryTranslation, clampToProgress(LINEAR, animationStartProgress,
                                     dismissTranslationInterpolationEnd));
-                    anim.add(taskView.getFocusTransitionScaleAndDimOutAnimator(),
+                    anim.add(taskView.getDismissIconFadeOutAnimator(),
                             clampToProgress(LINEAR, 0f, ANIMATION_DISMISS_PROGRESS_MIDPOINT));
                 } else if ((isFocusedTaskDismissed && nextFocusedTaskView != null && isSameGridRow(
                         taskView, nextFocusedTaskView))
@@ -3952,11 +4025,6 @@
                         startTranslation = isTaskViewVisible(taskView) ? 0
                                 : finalTranslation + (mIsRtl ? -mLastComputedTaskSize.right
                                         : mLastComputedTaskSize.right);
-                        animationStartProgress = Utilities.boundToRange(
-                                animationStartProgress
-                                        + splitTimings.getDesktopFadeSplitAnimationEndOffset(),
-                                0f,
-                                dismissTranslationInterpolationEnd);
                     }
                     Animator dismissAnimator = ObjectAnimator.ofFloat(taskView,
                             taskView.getPrimaryDismissTranslationProperty(),
@@ -4116,7 +4184,7 @@
                                     ? INVALID_TASK_ID
                                     : finalNextFocusedTaskView.getTaskViewId());
                             mTopRowIdSet.remove(mFocusedTaskViewId);
-                            finalNextFocusedTaskView.animateIconScaleAndDimIntoView();
+                            finalNextFocusedTaskView.getDismissIconFadeInAnimator().start();
                         }
                         updateTaskSize();
                         updateChildTaskOrientations();
@@ -4171,8 +4239,7 @@
                                     // Snap to latest large tile page after dismissing the
                                     // last grid task. This will prevent snapping to page 0 when
                                     // desktop task is visible as large tile.
-                                    pageToSnapTo = indexOfChild(
-                                            mUtils.getLastLargeTaskView(getTaskViews()));
+                                    pageToSnapTo = indexOfChild(mUtils.getLastLargeTaskView());
                                 }
                             } else if (taskViewIdToSnapTo != -1) {
                                 // If snapping to another page due to indices rearranging, find
@@ -4216,14 +4283,14 @@
      * - Current page is rightmost page (leftmost for RTL)
      * - Dragging an adjacent page on the left side (right side for RTL)
      */
-    private int getOffsetToDismissedTask(int scrollDiffPerPage, int dismissedIndex, int taskCount) {
-        // When mCurrentPage is ClearAllButton, use the last TaskView instead to calculate
-        // offset.
-        int currentPage = mCurrentPage == taskCount ? taskCount - 1 : mCurrentPage;
+    private int getOffsetToDismissedTask(int scrollDiffPerPage, int dismissedIndex,
+            int lastTaskViewIndex) {
+        // If `mCurrentPage` is beyond `lastTaskViewIndex`, use the last TaskView instead to
+        // calculate offset.
+        int currentPage = Math.min(mCurrentPage, lastTaskViewIndex);
         int offset = mIsRtl ? scrollDiffPerPage : 0;
         if (currentPage == dismissedIndex) {
-            int lastPage = taskCount - 1;
-            if (currentPage == lastPage) {
+            if (currentPage == lastTaskViewIndex) {
                 offset += mIsRtl ? -scrollDiffPerPage : scrollDiffPerPage;
             }
         } else {
@@ -4466,7 +4533,7 @@
 
         // Init task grid nav helper with top/bottom id arrays.
         TaskGridNavHelper taskGridNavHelper = new TaskGridNavHelper(getTopRowIdArray(),
-                getBottomRowIdArray(), mUtils.getLargeTaskViewIds(getTaskViews()));
+                getBottomRowIdArray(), mUtils.getLargeTaskViewIds());
 
         // Get current page's task view ID.
         TaskView currentPageTaskView = getCurrentPageTaskView();
@@ -4669,11 +4736,11 @@
 
     @Nullable
     public TaskView getLastLargeTaskView() {
-        return mUtils.getLastLargeTaskView(getTaskViews());
+        return mUtils.getLastLargeTaskView();
     }
 
     public int getLargeTilesCount() {
-        return mUtils.getLargeTileCount(getTaskViews());
+        return mUtils.getLargeTileCount();
     }
 
     @Nullable
@@ -4709,10 +4776,10 @@
     }
 
     /**
-     * Returns the current list of [TaskView] children.
+     * Returns iterable [TaskView] children.
      */
-    private Iterable<TaskView> getTaskViews() {
-        return mUtils.getTaskViews(getTaskViewCount(), this::requireTaskViewAt);
+    public TaskViewsIterable getTaskViews() {
+        return mTaskViewsIterable;
     }
 
     public void setOnEmptyMessageUpdatedListener(OnEmptyMessageUpdatedListener listener) {
@@ -4720,7 +4787,7 @@
     }
 
     public void updateEmptyMessage() {
-        boolean isEmpty = getTaskViewCount() == 0;
+        boolean isEmpty = !hasTaskViews();
         boolean hasSizeChanged = mLastMeasureSize.x != getWidth()
                 || mLastMeasureSize.y != getHeight();
         if (isEmpty == mShowEmptyMessage && !hasSizeChanged) {
@@ -4814,7 +4881,7 @@
         int modalMidpoint = getCurrentPage();
         TaskView carouselHiddenMidpointTask = runningTask != null ? runningTask
                 : mUtils.getFirstTaskViewInCarousel(/*nonRunningTaskCarouselHidden=*/true,
-                        getTaskViews(), null);
+                        /*runningTaskView=*/null);
         int carouselHiddenMidpoint = indexOfChild(carouselHiddenMidpointTask);
         boolean shouldCalculateOffsetForAllTasks = showAsGrid
                 && (enableGridOnlyOverview() || enableLargeDesktopWindowingTile())
@@ -5145,17 +5212,20 @@
         SplitAnimationTimings timings = AnimUtils.getDeviceOverviewToSplitTimings(
                 mContainer.getDeviceProfile().isTablet);
         if (enableLargeDesktopWindowingTile()) {
-            for (int i = 0; i < getTaskViewCount(); i++) {
-                TaskView taskView = requireTaskViewAt(i);
+            TaskView currentPageTaskView = getCurrentPageTaskView();
+            TaskView nextPageTaskView = getTaskViewAt(mCurrentPage + 1);
+            TaskView previousPageTaskView = getTaskViewAt(mCurrentPage - 1);
+            for (TaskView taskView : getTaskViews()) {
                 if (taskView instanceof DesktopTaskView) {
                     // Setting pivot to scale down from screen centre.
-                    if (i >= mCurrentPage - 1 && i <= mCurrentPage + 1) {
-                        float pivotX;
-                        if (i == mCurrentPage - 1) {
+                    if (taskView == previousPageTaskView || taskView == currentPageTaskView
+                            || taskView == nextPageTaskView) {
+                        float pivotX = 0f;
+                        if (taskView == previousPageTaskView) {
                             pivotX = mIsRtl ? taskView.getWidth() / 2f - mPageSpacing
                                     - taskView.getWidth()
                                     : taskView.getWidth() / 2f + mPageSpacing + taskView.getWidth();
-                        } else if (i == mCurrentPage) {
+                        } else if (taskView == currentPageTaskView) {
                             pivotX = taskView.getWidth() / 2f;
                         } else {
                             pivotX = mIsRtl ? taskView.getWidth() + mPageSpacing
@@ -5479,12 +5549,12 @@
 
         // Get the deadzone rect between the task views
         mTaskViewDeadZoneRect.setEmpty();
-        int count = getTaskViewCount();
-        if (count > 0) {
-            final View taskView = requireTaskViewAt(0);
-            requireTaskViewAt(count - 1).getHitRect(mTaskViewDeadZoneRect);
-            mTaskViewDeadZoneRect.union(taskView.getLeft(), taskView.getTop(), taskView.getRight(),
-                    taskView.getBottom());
+        if (hasTaskViews()) {
+            final View firstTaskView = getFirstTaskView();
+            mUtils.getLastTaskView().getHitRect(mTaskViewDeadZoneRect);
+            mTaskViewDeadZoneRect.union(firstTaskView.getLeft(), firstTaskView.getTop(),
+                    firstTaskView.getRight(),
+                    firstTaskView.getBottom());
         }
     }
 
@@ -5648,8 +5718,7 @@
             throw new IllegalStateException("Another pending animation is still running");
         }
 
-        int count = getTaskViewCount();
-        if (count == 0) {
+        if (!hasTaskViews()) {
             return new PendingAnimation(duration);
         }
 
@@ -5755,34 +5824,33 @@
 
     @Override
     public void addChildrenForAccessibility(ArrayList<View> outChildren) {
-        // Add children in reverse order
-        for (int i = getChildCount() - 1; i >= 0; --i) {
-            outChildren.add(getChildAt(i));
-        }
+        outChildren.addAll(getAccessibilityChildren());
+    }
+
+    public List<View> getAccessibilityChildren() {
+        return mUtils.getAccessibilityChildren();
     }
 
     @Override
     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
         super.onInitializeAccessibilityNodeInfo(info);
         final AccessibilityNodeInfo.CollectionInfo
-                collectionInfo = AccessibilityNodeInfo.CollectionInfo.obtain(
-                1, getTaskViewCount(), false,
-                AccessibilityNodeInfo.CollectionInfo.SELECTION_MODE_NONE);
+                collectionInfo = new AccessibilityNodeInfo.CollectionInfo(
+                1, getAccessibilityChildren().size(), false);
         info.setCollectionInfo(collectionInfo);
     }
 
     @Override
     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
         super.onInitializeAccessibilityEvent(event);
-
-        final int taskViewCount = getTaskViewCount();
-        event.setScrollable(taskViewCount > 0);
+        event.setScrollable(hasTaskViews());
 
         if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
+            final List<View> accessibilityChildren = getAccessibilityChildren();
             final int[] visibleTasks = getVisibleChildrenRange();
-            event.setFromIndex(taskViewCount - visibleTasks[1]);
-            event.setToIndex(taskViewCount - visibleTasks[0]);
-            event.setItemCount(taskViewCount);
+            event.setFromIndex(accessibilityChildren.indexOf(getChildAt(visibleTasks[1])));
+            event.setToIndex(accessibilityChildren.indexOf(getChildAt(visibleTasks[0])));
+            event.setItemCount(accessibilityChildren.size());
         }
     }
 
@@ -6008,7 +6076,7 @@
 
     @Override
     protected int computeMinScroll() {
-        if (getTaskViewCount() <= 0) {
+        if (!hasTaskViews()) {
             return super.computeMinScroll();
         }
 
@@ -6017,7 +6085,7 @@
 
     @Override
     protected int computeMaxScroll() {
-        if (getTaskViewCount() <= 0) {
+        if (!hasTaskViews()) {
             return super.computeMaxScroll();
         }
 
@@ -6029,17 +6097,15 @@
         if (mShowAsGridLastOnLayout) {
             // For grid Overview, it always start if a large tile (focused task or desktop task) if
             // they exist, otherwise it start with the first task.
-            TaskView firstLargeTaskView = mUtils.getFirstLargeTaskView(getTaskViews(),
-                    isSplitSelectionActive());
+            TaskView firstLargeTaskView = mUtils.getFirstLargeTaskView();
             if (firstLargeTaskView != null) {
                 firstView = firstLargeTaskView;
             } else {
-                firstView = mUtils.getFirstSmallTaskView(getTaskViews());
+                firstView = mUtils.getFirstSmallTaskView();
             }
         } else {
             firstView = mUtils.getFirstTaskViewInCarousel(
-                    /*nonRunningTaskCarouselHidden=*/mDesktopCarouselDetachProgress > 0,
-                    getTaskViews(), getRunningTaskView());
+                    /*nonRunningTaskCarouselHidden=*/mDesktopCarouselDetachProgress > 0);
         }
         return indexOfChild(firstView);
     }
@@ -6056,12 +6122,11 @@
             if (lastGridTaskView != null) {
                 lastView = lastGridTaskView;
             } else {
-                lastView = mUtils.getLastLargeTaskView(getTaskViews());
+                lastView = mUtils.getLastLargeTaskView();
             }
         } else {
             lastView = mUtils.getLastTaskViewInCarousel(
-                    /*nonRunningTaskCarouselHidden=*/mDesktopCarouselDetachProgress > 0,
-                    getTaskViews(), getRunningTaskView());
+                    /*nonRunningTaskCarouselHidden=*/mDesktopCarouselDetachProgress > 0);
         }
         return indexOfChild(lastView);
     }
@@ -6103,10 +6168,12 @@
             }
         }
 
-        final int taskCount = getTaskViewCount();
         int lastTaskScroll = getLastTaskScroll(clearAllScroll, clearAllWidth);
-        for (int i = 0; i < taskCount; i++) {
-            TaskView taskView = requireTaskViewAt(i);
+        for (int i = 0; i < getChildCount(); i++) {
+            TaskView taskView = getTaskViewAt(i);
+            if (taskView == null) {
+                continue;
+            }
             float scrollDiff = taskView.getScrollAdjustment(showAsGrid);
             int pageScroll = newPageScrolls[i] + Math.round(scrollDiff);
             if ((mIsRtl && pageScroll < lastTaskScroll)
@@ -6397,8 +6464,7 @@
             return;
         }
 
-        Map<Integer, ThumbnailData> updatedThumbnails = mUtils.screenshotTasks(taskView,
-                mRecentsAnimationController);
+        Map<Integer, ThumbnailData> updatedThumbnails = mUtils.screenshotTasks(taskView);
         if (enableRefactorTaskThumbnail()) {
             mHelper.switchToScreenshot(taskView, updatedThumbnails, onFinishRunnable);
         } else {
@@ -6468,6 +6534,12 @@
         return mSizeStrategy;
     }
 
+
+    /**
+     * Returns the container interface
+     */
+    protected abstract BaseContainerInterface<STATE_TYPE, ?> getContainerInterface(int displayId);
+
     /**
      * Set all the task views to color tint scrim mode, dimming or tinting them all. Allows the
      * tasks to be dimmed while other elements in the recents view are left alone.
@@ -6781,8 +6853,8 @@
         if (mDesktopRecentsTransitionController == null) {
             return;
         }
-        mDesktopRecentsTransitionController.moveToDesktop(taskContainer.getTask().key.id,
-                transitionSource);
+
+        mDesktopRecentsTransitionController.moveToDesktop(taskContainer, transitionSource);
         successCallback.run();
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java b/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java
index b04753b..a1d22fe 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java
@@ -25,7 +25,6 @@
 import android.view.View;
 import android.view.Window;
 
-import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.BaseActivity;
@@ -34,7 +33,6 @@
 import com.android.launcher3.taskbar.TaskbarUIController;
 import com.android.launcher3.views.ActivityContext;
 import com.android.launcher3.views.ScrimView;
-import com.android.quickstep.util.TISBindHelper;
 
 /**
  * Interface to be implemented by the parent view of RecentsView
@@ -217,6 +215,4 @@
     void setTaskbarUIController(@Nullable TaskbarUIController taskbarUIController);
 
     @Nullable TaskbarUIController getTaskbarUIController();
-
-    @NonNull TISBindHelper getTISBindHelper();
 }
diff --git a/quickstep/src/com/android/quickstep/views/RecentsViewModelHelper.kt b/quickstep/src/com/android/quickstep/views/RecentsViewModelHelper.kt
index 3616fbb..87771c6 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsViewModelHelper.kt
+++ b/quickstep/src/com/android/quickstep/views/RecentsViewModelHelper.kt
@@ -16,28 +16,24 @@
 
 package com.android.quickstep.views
 
+import com.android.launcher3.util.coroutines.DispatcherProvider
 import com.android.quickstep.ViewUtils
 import com.android.quickstep.recents.viewmodel.RecentsViewModel
 import com.android.systemui.shared.recents.model.ThumbnailData
-import kotlinx.coroutines.CoroutineName
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.SupervisorJob
 import kotlinx.coroutines.cancel
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
 
 /** Helper for [RecentsView] to interact with the [RecentsViewModel]. */
-class RecentsViewModelHelper(private val recentsViewModel: RecentsViewModel) {
-    private lateinit var viewAttachedScope: CoroutineScope
-
-    fun onAttachedToWindow() {
-        viewAttachedScope =
-            CoroutineScope(SupervisorJob() + Dispatchers.Default + CoroutineName("RecentsView"))
-    }
-
+class RecentsViewModelHelper(
+    private val recentsViewModel: RecentsViewModel,
+    private val recentsCoroutineScope: CoroutineScope,
+    private val dispatcherProvider: DispatcherProvider,
+) {
     fun onDetachedFromWindow() {
-        viewAttachedScope.cancel("RecentsView detaching from window")
+        recentsCoroutineScope.cancel("RecentsView detaching from window")
     }
 
     fun switchToScreenshot(
@@ -48,7 +44,7 @@
         // Update recentsViewModel and apply the thumbnailOverride ASAP, before waiting inside
         // viewAttachedScope.
         recentsViewModel.setRunningTaskShowScreenshot(true)
-        viewAttachedScope.launch {
+        recentsCoroutineScope.launch(dispatcherProvider.main) {
             recentsViewModel.waitForRunningTaskShowScreenshotToUpdate()
             recentsViewModel.waitForThumbnailsToUpdate(updatedThumbnails)
             withContext(Dispatchers.Main) { ViewUtils.postFrameDrawn(taskView, onFinishRunnable) }
diff --git a/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt b/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
new file mode 100644
index 0000000..ccf22ce
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.views
+
+import android.view.View
+import androidx.core.view.children
+import com.android.launcher3.Flags.enableLargeDesktopWindowingTile
+import com.android.quickstep.util.GroupTask
+import com.android.quickstep.views.RecentsView.RUNNING_TASK_ATTACH_ALPHA
+import com.android.systemui.shared.recents.model.ThumbnailData
+
+/**
+ * Helper class for [RecentsView]. This util class contains refactored and extracted functions from
+ * RecentsView to facilitate the implementation of unit tests.
+ */
+class RecentsViewUtils(private val recentsView: RecentsView<*, *>) {
+    /** Takes a screenshot of all [taskView] and return map of taskId to the screenshot */
+    fun screenshotTasks(taskView: TaskView): Map<Int, ThumbnailData> {
+        val recentsAnimationController = recentsView.recentsAnimationController ?: return emptyMap()
+        return taskView.taskContainers.associate {
+            it.task.key.id to recentsAnimationController.screenshotTask(it.task.key.id)
+        }
+    }
+
+    /**
+     * Sorts task groups to move desktop tasks to the end of the list.
+     *
+     * @param tasks List of group tasks to be sorted.
+     * @return Sorted list of GroupTasks to be used in the RecentsView.
+     */
+    fun sortDesktopTasksToFront(tasks: List<GroupTask>): List<GroupTask> {
+        val (desktopTasks, otherTasks) = tasks.partition { it.taskViewType == TaskViewType.DESKTOP }
+        return otherTasks + desktopTasks
+    }
+
+    /** Counts [TaskView]s that are [DesktopTaskView] instances. */
+    fun getDesktopTaskViewCount(): Int = recentsView.taskViews.count { it is DesktopTaskView }
+
+    /** Returns a list of all large TaskView Ids from [TaskView]s */
+    fun getLargeTaskViewIds(): List<Int> =
+        recentsView.taskViews.filter { it.isLargeTile }.map { it.taskViewId }
+
+    /** Counts [TaskView]s that are large tiles. */
+    fun getLargeTileCount(): Int = recentsView.taskViews.count { it.isLargeTile }
+
+    /** Returns the first TaskView that should be displayed as a large tile. */
+    fun getFirstLargeTaskView(): TaskView? =
+        recentsView.taskViews.firstOrNull {
+            it.isLargeTile && !(recentsView.isSplitSelectionActive && it is DesktopTaskView)
+        }
+
+    /** Returns the expected focus task. */
+    fun getExpectedFocusedTask(): TaskView? =
+        if (enableLargeDesktopWindowingTile())
+            recentsView.taskViews.firstOrNull { it !is DesktopTaskView }
+        else recentsView.taskViews.firstOrNull()
+
+    /**
+     * Returns the [TaskView] that should be the current page during task binding, in the following
+     * priorities:
+     * 1. Running task
+     * 2. Focused task
+     * 3. First non-desktop task
+     * 4. Last desktop task
+     * 5. null otherwise
+     */
+    fun getExpectedCurrentTask(runningTaskView: TaskView?, focusedTaskView: TaskView?): TaskView? =
+        runningTaskView
+            ?: focusedTaskView
+            ?: recentsView.taskViews.firstOrNull { it !is DesktopTaskView }
+            ?: recentsView.taskViews.lastOrNull()
+
+    /** Returns the first TaskView if it exists, or null otherwise. */
+    fun getFirstTaskView(): TaskView? = recentsView.taskViews.firstOrNull()
+
+    /** Returns the last TaskView if it exists, or null otherwise. */
+    fun getLastTaskView(): TaskView? = recentsView.taskViews.lastOrNull()
+
+    /** Returns the first TaskView that is not large */
+    fun getFirstSmallTaskView(): TaskView? = recentsView.taskViews.firstOrNull { !it.isLargeTile }
+
+    /** Returns the last TaskView that should be displayed as a large tile. */
+    fun getLastLargeTaskView(): TaskView? = recentsView.taskViews.lastOrNull { it.isLargeTile }
+
+    /**
+     * Gets the list of accessibility children. Currently all the children of RecentsViews are
+     * added, and in the reverse order to the list.
+     */
+    fun getAccessibilityChildren(): List<View> = recentsView.children.toList().reversed()
+
+    @JvmOverloads
+    /** Returns the first [TaskView], with some tasks possibly hidden in the carousel. */
+    fun getFirstTaskViewInCarousel(
+        nonRunningTaskCarouselHidden: Boolean,
+        runningTaskView: TaskView? = recentsView.runningTaskView,
+    ): TaskView? =
+        recentsView.taskViews.firstOrNull {
+            it.isVisibleInCarousel(runningTaskView, nonRunningTaskCarouselHidden)
+        }
+
+    /** Returns the last [TaskView], with some tasks possibly hidden in the carousel. */
+    fun getLastTaskViewInCarousel(nonRunningTaskCarouselHidden: Boolean): TaskView? =
+        recentsView.taskViews.lastOrNull {
+            it.isVisibleInCarousel(recentsView.runningTaskView, nonRunningTaskCarouselHidden)
+        }
+
+    /** Returns if any small tasks are fully visible */
+    fun isAnySmallTaskFullyVisible(): Boolean =
+        recentsView.taskViews.any { !it.isLargeTile && recentsView.isTaskViewFullyVisible(it) }
+
+    /** Apply attachAlpha to all [TaskView] accordingly to different conditions. */
+    fun applyAttachAlpha(nonRunningTaskCarouselHidden: Boolean) {
+        recentsView.taskViews.forEach { taskView ->
+            taskView.attachAlpha =
+                if (taskView == recentsView.runningTaskView) {
+                    RUNNING_TASK_ATTACH_ALPHA.get(recentsView)
+                } else {
+                    if (
+                        taskView.isVisibleInCarousel(
+                            recentsView.runningTaskView,
+                            nonRunningTaskCarouselHidden,
+                        )
+                    )
+                        1f
+                    else 0f
+                }
+        }
+    }
+
+    fun TaskView.isVisibleInCarousel(
+        runningTaskView: TaskView?,
+        nonRunningTaskCarouselHidden: Boolean,
+    ): Boolean =
+        if (!nonRunningTaskCarouselHidden) true
+        else getCarouselType() == runningTaskView.getCarouselType()
+
+    /** Returns the carousel type of the TaskView, and default to fullscreen if it's null. */
+    private fun TaskView?.getCarouselType(): TaskViewCarousel =
+        if (this is DesktopTaskView) TaskViewCarousel.DESKTOP else TaskViewCarousel.FULL_SCREEN
+
+    private enum class TaskViewCarousel {
+        FULL_SCREEN,
+        DESKTOP,
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index 082971c..0dbad70 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -31,7 +31,6 @@
 import android.util.FloatProperty
 import android.util.Log
 import android.view.Display
-import android.view.LayoutInflater
 import android.view.MotionEvent
 import android.view.View
 import android.view.View.OnClickListener
@@ -78,7 +77,6 @@
 import com.android.quickstep.TaskOverlayFactory
 import com.android.quickstep.TaskViewUtils
 import com.android.quickstep.orientation.RecentsPagedOrientationHandler
-import com.android.quickstep.task.thumbnail.TaskThumbnailView
 import com.android.quickstep.util.ActiveGestureErrorDetector
 import com.android.quickstep.util.ActiveGestureLog
 import com.android.quickstep.util.BorderAnimator
@@ -434,37 +432,54 @@
     // Used to cache thumbnail bounds to avoid recalculating on every hover move.
     private var thumbnailBounds = Rect()
 
-    private var focusTransitionProgress = 1f
+    // Progress variable indicating if the TaskView is in a settled state:
+    // 0 = The TaskView is in a transitioning state e.g. during gesture, in quickswitch carousel,
+    // becoming focus task etc.
+    // 1 = The TaskView is settled and no longer transitioning
+    private var settledProgress = 1f
         set(value) {
             field = value
-            onFocusTransitionProgressUpdated(field)
+            onSettledProgressUpdated(field)
         }
 
-    private val focusTransitionPropertyFactory =
+    private val settledProgressPropertyFactory =
         MultiPropertyFactory(
             this,
-            FOCUS_TRANSITION,
-            FOCUS_TRANSITION_INDEX_COUNT,
+            SETTLED_PROGRESS,
+            SETTLED_PROGRESS_INDEX_COUNT,
             { x: Float, y: Float -> x * y },
             1f,
         )
-    private val focusTransitionFullscreen =
-        focusTransitionPropertyFactory.get(FOCUS_TRANSITION_INDEX_FULLSCREEN)
-    private val focusTransitionScaleAndDim =
-        focusTransitionPropertyFactory.get(FOCUS_TRANSITION_INDEX_SCALE_AND_DIM)
+    private val settledProgressFullscreen =
+        settledProgressPropertyFactory.get(SETTLED_PROGRESS_INDEX_FULLSCREEN)
+    private val settledProgressGesture =
+        settledProgressPropertyFactory.get(SETTLED_PROGRESS_INDEX_GESTURE)
+    private val settledProgressDismiss =
+        settledProgressPropertyFactory.get(SETTLED_PROGRESS_INDEX_DISMISS)
 
     /**
-     * Returns an animator of [focusTransitionScaleAndDim] that transition out with a built-in
+     * Returns an animator of [settledProgressDismiss] that transition in with a built-in
      * interpolator.
      */
-    fun getFocusTransitionScaleAndDimOutAnimator(): ObjectAnimator =
+    fun getDismissIconFadeInAnimator(): ObjectAnimator =
+        ObjectAnimator.ofFloat(settledProgressDismiss, MULTI_PROPERTY_VALUE, 1f).apply {
+            duration = FADE_IN_ICON_DURATION
+            interpolator = FADE_IN_ICON_INTERPOLATOR
+        }
+
+    /**
+     * Returns an animator of [settledProgressDismiss] that transition out with a built-in
+     * interpolator. [AnimatedFloat] is used to apply another level of interpolation, on top of
+     * interpolator set to the [Animator] by the caller.
+     */
+    fun getDismissIconFadeOutAnimator(): ObjectAnimator =
         AnimatedFloat { v ->
-                focusTransitionScaleAndDim.value =
-                    FOCUS_TRANSITION_FAST_OUT_INTERPOLATOR.getInterpolation(v)
+                settledProgressDismiss.value =
+                    SETTLED_PROGRESS_FAST_OUT_INTERPOLATOR.getInterpolation(v)
             }
             .animateToValue(1f, 0f)
 
-    private var iconAndDimAnimator: ObjectAnimator? = null
+    private var iconFadeInOnGestureCompleteAnimator: ObjectAnimator? = null
     // The current background requests to load the task thumbnail and icon
     private val pendingThumbnailLoadRequests = mutableListOf<CancellableTask<*>>()
     private val pendingIconLoadRequests = mutableListOf<CancellableTask<*>>()
@@ -643,10 +658,10 @@
 
             recentsView?.let {
                 collectionItemInfo =
-                    AccessibilityNodeInfo.CollectionItemInfo.obtain(
+                    AccessibilityNodeInfo.CollectionItemInfo(
                         0,
                         1,
-                        it.taskViewCount - it.indexOfChild(this@TaskView) - 1,
+                        it.getAccessibilityChildren().indexOf(this@TaskView),
                         1,
                         false,
                     )
@@ -677,6 +692,28 @@
         return super.performAccessibilityAction(action, arguments)
     }
 
+    override fun onFinishInflate() {
+        super.onFinishInflate()
+        inflateViewStubs()
+    }
+
+    protected open fun inflateViewStubs() {
+        findViewById<ViewStub>(R.id.snapshot)
+            ?.apply {
+                layoutResource =
+                    if (enableRefactorTaskThumbnail()) R.layout.task_thumbnail
+                    else R.layout.task_thumbnail_deprecated
+            }
+            ?.inflate()
+        findViewById<ViewStub>(R.id.icon)
+            ?.apply {
+                layoutResource =
+                    if (enableOverviewIconMenu()) R.layout.icon_app_chip_view
+                    else R.layout.icon_view
+            }
+            ?.inflate()
+    }
+
     /** Updates this task view to the given {@param task}. */
     open fun bind(
         task: Task,
@@ -718,27 +755,11 @@
         @StagePosition stagePosition: Int,
         taskOverlayFactory: TaskOverlayFactory,
     ): TaskContainer {
-        val existingThumbnailView: View = findViewById(thumbnailViewId)!!
-        val snapshotView =
-            when {
-                !enableRefactorTaskThumbnail() -> existingThumbnailView
-                existingThumbnailView is TaskThumbnailView -> existingThumbnailView
-                else -> {
-                    val indexOfSnapshotView = indexOfChild(existingThumbnailView)
-                    LayoutInflater.from(context)
-                        .inflate(R.layout.task_thumbnail, this, false)
-                        .also {
-                            it.id = thumbnailViewId
-                            addView(it, indexOfSnapshotView, existingThumbnailView.layoutParams)
-                            removeView(existingThumbnailView)
-                        }
-                }
-            }
-        val iconView = getOrInflateIconView(iconViewId)
+        val iconView = findViewById<View>(iconViewId) as TaskViewIcon
         return TaskContainer(
             this,
             task,
-            snapshotView,
+            findViewById(thumbnailViewId),
             iconView,
             TransformingTouchDelegate(iconView.asView()),
             stagePosition,
@@ -748,18 +769,6 @@
         )
     }
 
-    protected fun getOrInflateIconView(@IdRes iconViewId: Int): TaskViewIcon {
-        val iconView = findViewById<View>(iconViewId)!!
-        return iconView as? TaskViewIcon
-            ?: (iconView as ViewStub)
-                .apply {
-                    layoutResource =
-                        if (enableOverviewIconMenu()) R.layout.icon_app_chip_view
-                        else R.layout.icon_view
-                }
-                .inflate() as TaskViewIcon
-    }
-
     fun containsMultipleTasks() = taskContainers.size > 1
 
     /**
@@ -1423,23 +1432,23 @@
      * Called to animate a smooth transition when going directly from an app into Overview (and vice
      * versa). Icons fade in, and DWB banners slide in with a "shift up" animation.
      */
-    private fun onFocusTransitionProgressUpdated(focusTransitionProgress: Float) {
+    private fun onSettledProgressUpdated(settledProgress: Float) {
         taskContainers.forEach {
-            it.iconView.setContentAlpha(focusTransitionProgress)
-            it.digitalWellBeingToast?.bannerOffsetPercentage = 1f - focusTransitionProgress
+            it.iconView.setContentAlpha(settledProgress)
+            it.digitalWellBeingToast?.bannerOffsetPercentage = 1f - settledProgress
         }
     }
 
-    fun animateIconScaleAndDimIntoView() {
-        iconAndDimAnimator?.cancel()
-        iconAndDimAnimator =
-            ObjectAnimator.ofFloat(focusTransitionScaleAndDim, MULTI_PROPERTY_VALUE, 0f, 1f).apply {
-                duration = SCALE_ICON_DURATION
+    fun startIconFadeInOnGestureComplete() {
+        iconFadeInOnGestureCompleteAnimator?.cancel()
+        iconFadeInOnGestureCompleteAnimator =
+            ObjectAnimator.ofFloat(settledProgressGesture, MULTI_PROPERTY_VALUE, 1f).apply {
+                duration = FADE_IN_ICON_DURATION
                 interpolator = Interpolators.LINEAR
                 addListener(
                     object : AnimatorListenerAdapter() {
                         override fun onAnimationEnd(animation: Animator) {
-                            iconAndDimAnimator = null
+                            iconFadeInOnGestureCompleteAnimator = null
                         }
                     }
                 )
@@ -1447,9 +1456,9 @@
             }
     }
 
-    fun setIconScaleAndDim(iconScale: Float) {
-        iconAndDimAnimator?.cancel()
-        focusTransitionScaleAndDim.value = iconScale
+    fun setIconVisibleForGesture(isVisible: Boolean) {
+        iconFadeInOnGestureCompleteAnimator?.cancel()
+        settledProgressGesture.value = if (isVisible) 1f else 0f
     }
 
     /** Set a color tint on the snapshot and supporting views. */
@@ -1544,8 +1553,8 @@
             it.iconView.setVisibility(if (fullscreenProgress < 1) VISIBLE else INVISIBLE)
             it.overlay.setFullscreenProgress(fullscreenProgress)
         }
-        focusTransitionFullscreen.value =
-            FOCUS_TRANSITION_FAST_OUT_INTERPOLATOR.getInterpolation(1 - fullscreenProgress)
+        settledProgressFullscreen.value =
+            SETTLED_PROGRESS_FAST_OUT_INTERPOLATOR.getInterpolation(1 - fullscreenProgress)
         updateFullscreenParams()
     }
 
@@ -1602,7 +1611,8 @@
         }
         dismissScale = 1f
         translationZ = 0f
-        setIconScaleAndDim(1f)
+        setIconVisibleForGesture(true)
+        settledProgressDismiss.value = 1f
         setColorTint(0f, 0)
     }
 
@@ -1630,9 +1640,10 @@
         const val FLAG_UPDATE_ALL =
             (FLAG_UPDATE_ICON or FLAG_UPDATE_THUMBNAIL or FLAG_UPDATE_CORNER_RADIUS)
 
-        const val FOCUS_TRANSITION_INDEX_FULLSCREEN = 0
-        const val FOCUS_TRANSITION_INDEX_SCALE_AND_DIM = 1
-        const val FOCUS_TRANSITION_INDEX_COUNT = 2
+        const val SETTLED_PROGRESS_INDEX_FULLSCREEN = 0
+        const val SETTLED_PROGRESS_INDEX_GESTURE = 1
+        const val SETTLED_PROGRESS_INDEX_DISMISS = 2
+        const val SETTLED_PROGRESS_INDEX_COUNT = 3
 
         private const val ALPHA_INDEX_STABLE = 0
         private const val ALPHA_INDEX_ATTACH = 1
@@ -1642,25 +1653,26 @@
 
         /** The maximum amount that a task view can be scrimmed, dimmed or tinted. */
         const val MAX_PAGE_SCRIM_ALPHA = 0.4f
-        const val SCALE_ICON_DURATION: Long = 120
+        const val FADE_IN_ICON_DURATION: Long = 120
         private const val DIM_ANIM_DURATION: Long = 700
-        private const val FOCUS_TRANSITION_THRESHOLD =
-            SCALE_ICON_DURATION.toFloat() / DIM_ANIM_DURATION
-        val FOCUS_TRANSITION_FAST_OUT_INTERPOLATOR =
+        private const val SETTLE_TRANSITION_THRESHOLD =
+            FADE_IN_ICON_DURATION.toFloat() / DIM_ANIM_DURATION
+        val SETTLED_PROGRESS_FAST_OUT_INTERPOLATOR =
             Interpolators.clampToProgress(
                 Interpolators.FAST_OUT_SLOW_IN,
-                1f - FOCUS_TRANSITION_THRESHOLD,
+                1f - SETTLE_TRANSITION_THRESHOLD,
                 1f,
             )!!
+        private val FADE_IN_ICON_INTERPOLATOR = Interpolators.LINEAR
         private val SYSTEM_GESTURE_EXCLUSION_RECT = listOf(Rect())
 
-        private val FOCUS_TRANSITION: FloatProperty<TaskView> =
-            object : FloatProperty<TaskView>("focusTransition") {
+        private val SETTLED_PROGRESS: FloatProperty<TaskView> =
+            object : FloatProperty<TaskView>("settleTransition") {
                 override fun setValue(taskView: TaskView, v: Float) {
-                    taskView.focusTransitionProgress = v
+                    taskView.settledProgress = v
                 }
 
-                override fun get(taskView: TaskView) = taskView.focusTransitionProgress
+                override fun get(taskView: TaskView) = taskView.settledProgress
             }
 
         private val SPLIT_SELECT_TRANSLATION_X: FloatProperty<TaskView> =
diff --git a/quickstep/src/com/android/quickstep/views/TaskViewIcon.java b/quickstep/src/com/android/quickstep/views/TaskViewIcon.java
index 94739cb..80e3a2b 100644
--- a/quickstep/src/com/android/quickstep/views/TaskViewIcon.java
+++ b/quickstep/src/com/android/quickstep/views/TaskViewIcon.java
@@ -48,6 +48,11 @@
     void setModalAlpha(float alpha);
 
     /**
+     * Sets the opacity of the view for flex split state.
+     */
+    void setFlexSplitAlpha(float alpha);
+
+    /**
      * Returns this icon view's drawable.
      */
     @Nullable Drawable getDrawable();
diff --git a/quickstep/src_protolog/com/android/launcher3/util/StateManagerProtoLogProxy.java b/quickstep/src_protolog/com/android/launcher3/util/StateManagerProtoLogProxy.java
index bc989dc..c319cb1 100644
--- a/quickstep/src_protolog/com/android/launcher3/util/StateManagerProtoLogProxy.java
+++ b/quickstep/src_protolog/com/android/launcher3/util/StateManagerProtoLogProxy.java
@@ -18,6 +18,7 @@
 
 import static com.android.launcher3.Flags.enableStateManagerProtoLog;
 import static com.android.quickstep.util.QuickstepProtoLogGroup.LAUNCHER_STATE_MANAGER;
+import static com.android.quickstep.util.QuickstepProtoLogGroup.isProtoLogInitialized;
 
 import androidx.annotation.NonNull;
 
@@ -30,7 +31,7 @@
 
     public static void logGoToState(
             @NonNull Object fromState, @NonNull Object toState, @NonNull String trace) {
-        if (!enableStateManagerProtoLog()) return;
+        if (!enableStateManagerProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(LAUNCHER_STATE_MANAGER,
                 "StateManager.goToState: fromState: %s, toState: %s, partial trace:\n%s",
                 fromState,
@@ -40,7 +41,7 @@
 
     public static void logCreateAtomicAnimation(
             @NonNull Object fromState, @NonNull Object toState, @NonNull String trace) {
-        if (!enableStateManagerProtoLog()) return;
+        if (!enableStateManagerProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(LAUNCHER_STATE_MANAGER, "StateManager.createAtomicAnimation: "
                         + "fromState: %s, toState: %s, partial trace:\n%s",
                 fromState,
@@ -49,17 +50,17 @@
     }
 
     public static void logOnStateTransitionStart(@NonNull Object state) {
-        if (!enableStateManagerProtoLog()) return;
+        if (!enableStateManagerProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(LAUNCHER_STATE_MANAGER, "StateManager.onStateTransitionStart: state: %s", state);
     }
 
     public static void logOnStateTransitionEnd(@NonNull Object state) {
-        if (!enableStateManagerProtoLog()) return;
+        if (!enableStateManagerProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(LAUNCHER_STATE_MANAGER, "StateManager.onStateTransitionEnd: state: %s", state);
     }
 
     public static void logCancelAnimation(boolean animationOngoing, @NonNull String trace) {
-        if (!enableStateManagerProtoLog()) return;
+        if (!enableStateManagerProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(LAUNCHER_STATE_MANAGER,
                 "StateManager.cancelAnimation: animation ongoing: %b, partial trace:\n%s",
                 animationOngoing,
diff --git a/quickstep/src_protolog/com/android/quickstep/util/ActiveGestureProtoLogProxy.java b/quickstep/src_protolog/com/android/quickstep/util/ActiveGestureProtoLogProxy.java
index f25f6f4..be1a4e8 100644
--- a/quickstep/src_protolog/com/android/quickstep/util/ActiveGestureProtoLogProxy.java
+++ b/quickstep/src_protolog/com/android/quickstep/util/ActiveGestureProtoLogProxy.java
@@ -37,6 +37,7 @@
 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.SET_END_TARGET;
 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.START_RECENTS_ANIMATION;
 import static com.android.quickstep.util.QuickstepProtoLogGroup.ACTIVE_GESTURE_LOG;
+import static com.android.quickstep.util.QuickstepProtoLogGroup.isProtoLogInitialized;
 
 import android.graphics.Point;
 import android.graphics.RectF;
@@ -62,7 +63,7 @@
 
     public static void logLauncherDestroyed() {
         ActiveGestureLog.INSTANCE.addLog("Launcher destroyed", LAUNCHER_DESTROYED);
-        if (!enableActiveGestureProtoLog()) return;
+        if (isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG, "Launcher destroyed");
     }
 
@@ -70,7 +71,7 @@
         ActiveGestureLog.INSTANCE.addLog(
                 /* event= */ "AbsSwipeUpHandler.onRecentsAnimationCanceled",
                 /* gestureEvent= */ CANCEL_RECENTS_ANIMATION);
-        if (!enableActiveGestureProtoLog()) return;
+        if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG, "AbsSwipeUpHandler.onRecentsAnimationCanceled");
     }
 
@@ -78,7 +79,7 @@
         ActiveGestureLog.INSTANCE.addLog(
                 /* event= */ "RecentsAnimationCallbacks.onAnimationFinished",
                 ON_FINISH_RECENTS_ANIMATION);
-        if (!enableActiveGestureProtoLog()) return;
+        if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG, "AbsSwipeUpHandler.onAnimationFinished");
     }
 
@@ -86,27 +87,27 @@
         ActiveGestureLog.INSTANCE.addLog(
                 "AbsSwipeUpHandler.cancelCurrentAnimation",
                 ActiveGestureErrorDetector.GestureEvent.CANCEL_CURRENT_ANIMATION);
-        if (!enableActiveGestureProtoLog()) return;
+        if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG, "AbsSwipeUpHandler.cancelCurrentAnimation");
     }
 
     public static void logAbsSwipeUpHandlerOnTasksAppeared() {
         ActiveGestureLog.INSTANCE.addLog("AbsSwipeUpHandler.onTasksAppeared: "
                 + "force finish recents animation complete; clearing state callback.");
-        if (!enableActiveGestureProtoLog()) return;
+        if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG, "AbsSwipeUpHandler.onTasksAppeared: "
                 + "force finish recents animation complete; clearing state callback.");
     }
 
     public static void logHandOffAnimation() {
         ActiveGestureLog.INSTANCE.addLog("AbsSwipeUpHandler.handOffAnimation");
-        if (!enableActiveGestureProtoLog()) return;
+        if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG, "AbsSwipeUpHandler.handOffAnimation");
     }
 
     public static void logFinishRecentsAnimationOnTasksAppeared() {
         ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimationOnTasksAppeared");
-        if (!enableActiveGestureProtoLog()) return;
+        if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG, "finishRecentsAnimationOnTasksAppeared");
     }
 
@@ -114,14 +115,14 @@
         ActiveGestureLog.INSTANCE.addLog(
                 /* event= */ "RecentsAnimationCallbacks.onAnimationCanceled",
                 /* gestureEvent= */ ON_CANCEL_RECENTS_ANIMATION);
-        if (!enableActiveGestureProtoLog()) return;
+        if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG, "RecentsAnimationCallbacks.onAnimationCanceled");
     }
 
     public static void logRecentsAnimationCallbacksOnTasksAppeared() {
         ActiveGestureLog.INSTANCE.addLog("RecentsAnimationCallbacks.onTasksAppeared",
                 ActiveGestureErrorDetector.GestureEvent.TASK_APPEARED);
-        if (!enableActiveGestureProtoLog()) return;
+        if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG, "RecentsAnimationCallbacks.onTasksAppeared");
     }
 
@@ -129,39 +130,39 @@
         ActiveGestureLog.INSTANCE.addLog(
                 /* event= */ "TaskAnimationManager.startRecentsAnimation",
                 /* gestureEvent= */ START_RECENTS_ANIMATION);
-        if (!enableActiveGestureProtoLog()) return;
+        if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG, "TaskAnimationManager.startRecentsAnimation");
     }
 
     public static void logLaunchingSideTaskFailed() {
         ActiveGestureLog.INSTANCE.addLog("Unable to launch side task (no recents)");
-        if (!enableActiveGestureProtoLog()) return;
+        if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG, "Unable to launch side task (no recents)");
     }
 
     public static void logContinueRecentsAnimation() {
         ActiveGestureLog.INSTANCE.addLog(/* event= */ "continueRecentsAnimation");
-        if (!enableActiveGestureProtoLog()) return;
+        if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG, "continueRecentsAnimation");
     }
 
     public static void logCleanUpRecentsAnimationSkipped() {
         ActiveGestureLog.INSTANCE.addLog(
                 /* event= */ "cleanUpRecentsAnimation skipped due to wrong callbacks");
-        if (!enableActiveGestureProtoLog()) return;
+        if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG, "cleanUpRecentsAnimation skipped due to wrong callbacks");
     }
 
     public static void logCleanUpRecentsAnimation() {
         ActiveGestureLog.INSTANCE.addLog(/* event= */ "cleanUpRecentsAnimation");
-        if (!enableActiveGestureProtoLog()) return;
+        if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG, "cleanUpRecentsAnimation");
     }
 
     public static void logOnInputEventUserLocked() {
         ActiveGestureLog.INSTANCE.addLog(
                 "TIS.onInputEvent: Cannot process input event: user is locked");
-        if (!enableActiveGestureProtoLog()) return;
+        if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG,
                 "TIS.onInputEvent: Cannot process input event: user is locked");
     }
@@ -171,7 +172,7 @@
                         + "but a previously-requested recents animation hasn't started. "
                         + "Ignoring all following motion events.",
                 RECENTS_ANIMATION_START_PENDING);
-        if (!enableActiveGestureProtoLog()) return;
+        if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG, "TIS.onMotionEvent: A new gesture has been started, "
                 + "but a previously-requested recents animation hasn't started. "
                 + "Ignoring all following motion events.");
@@ -180,53 +181,53 @@
     public static void logOnInputEventThreeButtonNav() {
         ActiveGestureLog.INSTANCE.addLog("TIS.onInputEvent: Cannot process input event: "
                 + "using 3-button nav and event is not a trackpad event");
-        if (!enableActiveGestureProtoLog()) return;
+        if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG, "TIS.onInputEvent: Cannot process input event: "
                 + "using 3-button nav and event is not a trackpad event");
     }
 
     public static void logPreloadRecentsAnimation() {
         ActiveGestureLog.INSTANCE.addLog("preloadRecentsAnimation");
-        if (!enableActiveGestureProtoLog()) return;
+        if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG, "preloadRecentsAnimation");
     }
 
     public static void logRecentTasksMissing() {
         ActiveGestureLog.INSTANCE.addLog("Null mRecentTasks", RECENT_TASKS_MISSING);
-        if (!enableActiveGestureProtoLog()) return;
+        if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG, "Null mRecentTasks");
     }
 
     public static void logExecuteHomeCommand() {
         ActiveGestureLog.INSTANCE.addLog("OverviewCommandHelper.executeCommand(HOME)");
-        if (!enableActiveGestureProtoLog()) return;
+        if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG, "OverviewCommandHelper.executeCommand(HOME)");
     }
 
     public static void logFinishRecentsAnimationCallback() {
         ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation-callback");
-        if (!enableActiveGestureProtoLog()) return;
+        if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG, "finishRecentsAnimation-callback");
     }
 
     public static void logOnScrollerAnimationAborted() {
         ActiveGestureLog.INSTANCE.addLog("scroller animation aborted",
                 ActiveGestureErrorDetector.GestureEvent.SCROLLER_ANIMATION_ABORTED);
-        if (!enableActiveGestureProtoLog()) return;
+        if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG, "scroller animation aborted");
     }
 
     public static void logInputConsumerBecameActive(@NonNull String consumerName) {
         ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
                 "%s became active", consumerName));
-        if (!enableActiveGestureProtoLog()) return;
+        if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG, "%s became active", consumerName);
     }
 
     public static void logTaskLaunchFailed(int launchedTaskId) {
         ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
                 "Launch failed, task (id=%d) finished mid transition", launchedTaskId));
-        if (!enableActiveGestureProtoLog()) return;
+        if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG,
                 "Launch failed, task (id=%d) finished mid transition", launchedTaskId);
     }
@@ -234,7 +235,7 @@
     public static void logOnPageEndTransition(int nextPageIndex) {
         ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
                 "onPageEndTransition: current page index updated: %d", nextPageIndex));
-        if (!enableActiveGestureProtoLog()) return;
+        if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG,
                 "onPageEndTransition: current page index updated: %d", nextPageIndex);
     }
@@ -244,7 +245,7 @@
                 "Quick switch from home fallback case: The TaskView at index %d is missing.",
                         taskIndex),
                 QUICK_SWITCH_FROM_HOME_FALLBACK);
-        if (!enableActiveGestureProtoLog()) return;
+        if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG,
                 "Quick switch from home fallback case: The TaskView at index %d is missing.",
                 taskIndex);
@@ -255,7 +256,7 @@
                 "Quick switch from home failed: TaskViews at indices %d and 0 are missing.",
                         taskIndex),
                 QUICK_SWITCH_FROM_HOME_FAILED);
-        if (!enableActiveGestureProtoLog()) return;
+        if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG,
                 "Quick switch from home failed: TaskViews at indices %d and 0 are missing.",
                 taskIndex);
@@ -265,42 +266,42 @@
         ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
                 "finishRecentsAnimation: %b", toRecents),
                 /* gestureEvent= */ FINISH_RECENTS_ANIMATION);
-        if (!enableActiveGestureProtoLog()) return;
+        if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG, "finishRecentsAnimation: %b", toRecents);
     }
 
     public static void logSetEndTarget(@NonNull String target) {
         ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
                 "setEndTarget %s", target), /* gestureEvent= */ SET_END_TARGET);
-        if (!enableActiveGestureProtoLog()) return;
+        if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG, "setEndTarget %s", target);
     }
 
     public static void logStartHomeIntent(@NonNull String reason) {
         ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
                 "OverviewComponentObserver.startHomeIntent: %s", reason));
-        if (!enableActiveGestureProtoLog()) return;
+        if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG, "OverviewComponentObserver.startHomeIntent: %s", reason);
     }
 
     public static void logRunningTaskPackage(@NonNull String packageName) {
         ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
                 "Current running task package name=%s", packageName));
-        if (!enableActiveGestureProtoLog()) return;
+        if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG, "Current running task package name=%s", packageName);
     }
 
     public static void logSysuiStateFlags(@NonNull String stateFlags) {
         ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
                 "Current SystemUi state flags=%s", stateFlags));
-        if (!enableActiveGestureProtoLog()) return;
+        if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG, "Current SystemUi state flags=%s", stateFlags);
     }
 
     public static void logSetInputConsumer(@NonNull String consumerName, @NonNull String reason) {
         ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
                 "setInputConsumer: %s. reason(s):%s", consumerName, reason));
-        if (!enableActiveGestureProtoLog()) return;
+        if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG,
                 "setInputConsumer: %s. reason(s):%s", consumerName, reason);
     }
@@ -312,7 +313,7 @@
                         + "one (%s) was excluded from recents",
                 otherTaskPackage,
                 runningTaskPackage));
-        if (!enableActiveGestureProtoLog()) return;
+        if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG,
                 "Changing active task to %s because the previous task running on top of this "
                         + "one (%s) was excluded from recents",
@@ -328,7 +329,7 @@
                 /* gestureEvent= */ action == ACTION_DOWN
                         ? MOTION_DOWN
                         : MOTION_UP);
-        if (!enableActiveGestureProtoLog()) return;
+        if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG,
                 "onMotionEvent(%d, %d): %s, %s", x, y, actionString, classification);
     }
@@ -341,7 +342,7 @@
                         classification,
                         pointerCount),
                 MOTION_MOVE);
-        if (!enableActiveGestureProtoLog()) return;
+        if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG,
                 "onMotionEvent: %s, %s, pointerCount: %d", action, classification, pointerCount);
     }
@@ -350,7 +351,7 @@
             @NonNull String action, @NonNull String classification) {
         ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
                 "onMotionEvent: %s, %s", action, classification));
-        if (!enableActiveGestureProtoLog()) return;
+        if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG, "onMotionEvent: %s, %s", action, classification);
     }
 
@@ -362,7 +363,7 @@
                         startNavMode,
                         currentNavMode),
                 NAVIGATION_MODE_SWITCHED);
-        if (!enableActiveGestureProtoLog()) return;
+        if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG,
                 "TIS.onInputEvent: Navigation mode switched mid-gesture (%s -> %s); "
                         + "cancelling gesture.",
@@ -373,7 +374,7 @@
     public static void logUnknownInputEvent(@NonNull String event) {
         ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
                 "TIS.onInputEvent: Cannot process input event: received unknown event %s", event));
-        if (!enableActiveGestureProtoLog()) return;
+        if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG,
                 "TIS.onInputEvent: Cannot process input event: received unknown event %s", event);
     }
@@ -381,14 +382,14 @@
     public static void logFinishRunningRecentsAnimation(boolean toHome) {
         ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
                 "finishRunningRecentsAnimation: %b", toHome));
-        if (!enableActiveGestureProtoLog()) return;
+        if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG, "finishRunningRecentsAnimation: %b", toHome);
     }
 
     public static void logOnRecentsAnimationStartCancelled() {
         ActiveGestureLog.INSTANCE.addLog("RecentsAnimationCallbacks.onAnimationStart (canceled): 0",
                 /* gestureEvent= */ ON_START_RECENTS_ANIMATION);
-        if (!enableActiveGestureProtoLog()) return;
+        if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG, "RecentsAnimationCallbacks.onAnimationStart (canceled): 0");
     }
 
@@ -396,7 +397,7 @@
         ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
                 "RecentsAnimationCallbacks.onAnimationStart (canceled): %d", appCount),
                 /* gestureEvent= */ ON_START_RECENTS_ANIMATION);
-        if (!enableActiveGestureProtoLog()) return;
+        if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG,
                 "RecentsAnimationCallbacks.onAnimationStart (canceled): %d", appCount);
     }
@@ -406,7 +407,7 @@
                 "TaskAnimationManager.startRecentsAnimation(%s): "
                         + "Setting mRecentsAnimationStartPending = false",
                 callback));
-        if (!enableActiveGestureProtoLog()) return;
+        if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG,
                 "TaskAnimationManager.startRecentsAnimation(%s): "
                         + "Setting mRecentsAnimationStartPending = false",
@@ -418,7 +419,7 @@
                 "TaskAnimationManager.startRecentsAnimation: "
                         + "Setting mRecentsAnimationStartPending = %b",
                 value));
-        if (!enableActiveGestureProtoLog()) return;
+        if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG,
                 "TaskAnimationManager.startRecentsAnimation: "
                         + "Setting mRecentsAnimationStartPending = %b",
@@ -428,28 +429,28 @@
     public static void logLaunchingSideTask(int taskId) {
         ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
                 "Launching side task id=%d", taskId));
-        if (!enableActiveGestureProtoLog()) return;
+        if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG, "Launching side task id=%d", taskId);
     }
 
     public static void logOnInputEventActionDown(@NonNull ActiveGestureLog.CompoundString reason) {
         ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
                 "TIS.onMotionEvent: ").append(reason));
-        if (!enableActiveGestureProtoLog()) return;
+        if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG, "TIS.onMotionEvent: %s", reason.toString());
     }
 
     public static void logStartNewTask(@NonNull ActiveGestureLog.CompoundString tasks) {
         ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
                 "Launching task: ").append(tasks));
-        if (!enableActiveGestureProtoLog()) return;
+        if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG, "TIS.onMotionEvent: %s", tasks.toString());
     }
 
     public static void logMotionPauseDetectorEvent(@NonNull ActiveGestureLog.CompoundString event) {
         ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
                 "MotionPauseDetector: ").append(event));
-        if (!enableActiveGestureProtoLog()) return;
+        if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG, "MotionPauseDetector: %s", event.toString());
     }
 
@@ -457,7 +458,7 @@
             @NonNull ActiveGestureLog.CompoundString reason) {
         ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
                 "handleTaskAppeared check failed: ").append(reason));
-        if (!enableActiveGestureProtoLog()) return;
+        if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG, "handleTaskAppeared check failed: %s", reason.toString());
     }
 
@@ -469,7 +470,7 @@
             @NonNull String string,
             @Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) {
         ActiveGestureLog.INSTANCE.addLog(string, gestureEvent);
-        if (!enableActiveGestureProtoLog()) return;
+        if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG, "%s", string);
     }
 
@@ -477,7 +478,7 @@
         ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
                 "onSettledOnEndTarget %s", endTarget),
                 /* gestureEvent= */ ON_SETTLED_ON_END_TARGET);
-        if (!enableActiveGestureProtoLog()) return;
+        if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG, "onSettledOnEndTarget %s", endTarget);
     }
 
@@ -488,7 +489,7 @@
                         velocityY,
                         angle),
                 velocityX == 0 && velocityY == 0 ? INVALID_VELOCITY_ON_SWIPE_UP : null);
-        if (!enableActiveGestureProtoLog()) return;
+        if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG,
                 "calculateEndTarget: velocities=(x=%fdp/ms, y=%fdp/ms), angle=%f",
                 velocityX,
@@ -501,7 +502,7 @@
                 "Forcefully finishing recents animation: Unexpected task appeared id=%d, pkg=%s",
                 taskId,
                 packageName));
-        if (!enableActiveGestureProtoLog()) return;
+        if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG,
                 "Forcefully finishing recents animation: Unexpected task appeared id=%d, pkg=%s",
                 taskId,
@@ -511,7 +512,7 @@
     public static void logCreateTouchRegionForDisplay(int displayRotation,
             @NonNull Point displaySize, @NonNull RectF swipeRegion, @NonNull RectF ohmRegion,
             int gesturalHeight, int largerGesturalHeight, @NonNull String reason) {
-        if (!enableActiveGestureProtoLog()) return;
+        if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG, 
                 "OrientationTouchTransformer.createRegionForDisplay: "
                         + "dispRot=%d, dispSize=%s, swipeRegion=%s, ohmRegion=%s, "
diff --git a/quickstep/src_protolog/com/android/quickstep/util/QuickstepProtoLogGroup.java b/quickstep/src_protolog/com/android/quickstep/util/QuickstepProtoLogGroup.java
index bb02a11..2327cfc 100644
--- a/quickstep/src_protolog/com/android/quickstep/util/QuickstepProtoLogGroup.java
+++ b/quickstep/src_protolog/com/android/quickstep/util/QuickstepProtoLogGroup.java
@@ -16,6 +16,8 @@
 
 package com.android.quickstep.util;
 
+import android.util.Log;
+
 import androidx.annotation.NonNull;
 
 import com.android.internal.protolog.ProtoLog;
@@ -26,7 +28,7 @@
 /** Enums used to interface with the ProtoLog API. */
 public enum QuickstepProtoLogGroup implements IProtoLogGroup {
 
-    ACTIVE_GESTURE_LOG(true, true, false, "ActiveGestureLog"),
+    ACTIVE_GESTURE_LOG(true, true, Constants.DEBUG_ACTIVE_GESTURE, "ActiveGestureLog"),
     RECENTS_WINDOW(true, true, Constants.DEBUG_RECENTS_WINDOW, "RecentsWindow"),
     LAUNCHER_STATE_MANAGER(true, true, Constants.DEBUG_STATE_MANAGER, "LauncherStateManager");
 
@@ -35,7 +37,23 @@
     private volatile boolean mLogToLogcat;
     private final @NonNull String mTag;
 
+    public static boolean isProtoLogInitialized() {
+        if (!Variables.sIsInitialized) {
+            Log.w(Constants.TAG,
+                    "Attempting to log to ProtoLog before initializing it.",
+                    new IllegalStateException());
+        }
+        return Variables.sIsInitialized;
+    }
+
     public static void initProtoLog() {
+        if (Variables.sIsInitialized) {
+            Log.e(Constants.TAG,
+                    "Attempting to re-initialize ProtoLog.", new IllegalStateException());
+            return;
+        }
+        Log.i(Constants.TAG, "Initializing ProtoLog.");
+        Variables.sIsInitialized = true;
         ProtoLog.init(QuickstepProtoLogGroup.values());
     }
 
@@ -95,8 +113,16 @@
         this.mLogToLogcat = logToLogcat;
     }
 
+    private static final class Variables {
+
+        private static boolean sIsInitialized = false;
+    }
+
     private static final class Constants {
 
+        private static final String TAG = "QuickstepProtoLogGroup";
+
+        private static final boolean DEBUG_ACTIVE_GESTURE = false;
         private static final boolean DEBUG_RECENTS_WINDOW = false;
         private static final boolean DEBUG_STATE_MANAGER = true; // b/279059025, b/325463989
 
diff --git a/quickstep/src_protolog/com/android/quickstep/util/RecentsWindowProtoLogProxy.java b/quickstep/src_protolog/com/android/quickstep/util/RecentsWindowProtoLogProxy.java
index f54ad67..2c9ae33 100644
--- a/quickstep/src_protolog/com/android/quickstep/util/RecentsWindowProtoLogProxy.java
+++ b/quickstep/src_protolog/com/android/quickstep/util/RecentsWindowProtoLogProxy.java
@@ -18,6 +18,7 @@
 
 import static com.android.launcher3.Flags.enableRecentsWindowProtoLog;
 import static com.android.quickstep.util.QuickstepProtoLogGroup.RECENTS_WINDOW;
+import static com.android.quickstep.util.QuickstepProtoLogGroup.isProtoLogInitialized;
 
 import androidx.annotation.NonNull;
 
@@ -36,17 +37,17 @@
 public class RecentsWindowProtoLogProxy {
 
     public static void logOnStateSetStart(@NonNull String stateName) {
-        if (!enableRecentsWindowProtoLog()) return;
+        if (!enableRecentsWindowProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(RECENTS_WINDOW, "onStateSetStart: %s", stateName);
     }
 
     public static void logOnStateSetEnd(@NonNull String stateName) {
-        if (!enableRecentsWindowProtoLog()) return;
+        if (!enableRecentsWindowProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(RECENTS_WINDOW, "onStateSetEnd: %s", stateName);
     }
 
     public static void logStartRecentsWindow(boolean isShown, boolean windowViewIsNull) {
-        if (!enableRecentsWindowProtoLog()) return;
+        if (!enableRecentsWindowProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(RECENTS_WINDOW,
                 "Starting recents window: isShow= %b, windowViewIsNull=%b",
                 isShown,
@@ -54,7 +55,7 @@
     }
 
     public static void logCleanup(boolean isShown) {
-        if (!enableRecentsWindowProtoLog()) return;
+        if (!enableRecentsWindowProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(RECENTS_WINDOW, "Cleaning up recents window: isShow= %b", isShown);
     }
 }
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt
index 011ba7e..eb13b55 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt
@@ -22,7 +22,9 @@
 import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.SetFlagsRule
 import com.android.launcher3.Flags.FLAG_TASKBAR_OVERFLOW
+import com.android.launcher3.R
 import com.android.launcher3.taskbar.TaskbarControllerTestUtil.runOnMainSync
+import com.android.launcher3.taskbar.TaskbarViewTestUtil.createHotseatItems
 import com.android.launcher3.taskbar.bubbles.BubbleBarViewController
 import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController
 import com.android.launcher3.taskbar.rules.MockedRecentsModelTestRule
@@ -137,6 +139,54 @@
 
     @Test
     @TaskbarMode(PINNED)
+    fun testOverflownTaskbarWithNoSpaceForRecentApps_pinned() {
+        val initialIconCount = currentNumberOfTaskbarIcons.coerceAtLeast(2)
+
+        // Create two "recent" desktop tasks, and then add enough hotseat items so the taskbar
+        // reaches max number of items with hotseat item icons, all apps and divider icons only.
+        // I.e. so all desktop tasks are in taskbar overflow.
+        createDesktopTask(2)
+        runOnMainSync {
+            val taskbarView: TaskbarView =
+                taskbarUnitTestRule.activityContext.dragLayer.findViewById(R.id.taskbar_view)
+            taskbarView.updateItems(
+                createHotseatItems(maxNumberOfTaskbarIcons - initialIconCount),
+                recentAppsController.shownTasks,
+            )
+        }
+
+        // Verify that taskbar overflow view is shown (eventhough it exceeds max taskbar icons).
+        assertThat(currentNumberOfTaskbarIcons).isEqualTo(maxNumberOfTaskbarIcons + 1)
+        assertThat(taskbarOverflowIconIndex).isEqualTo(maxNumberOfTaskbarIcons)
+        assertThat(overflowItems).containsExactlyElementsIn(0..1)
+    }
+
+    @Test
+    @TaskbarMode(PINNED)
+    fun testOverflownTaskbarWithNoSpaceForRecentApps_singleRecent_pinned() {
+        val initialIconCount = currentNumberOfTaskbarIcons.coerceAtLeast(2)
+
+        // Create a "recent" desktop task, and then add enough hotseat items so the taskbar
+        // reaches max number of items with hotseat item icons, all apps and divider icons only.
+        // I.e. so the single desktop tasks is in taskbar overflow.
+        createDesktopTask(1)
+        runOnMainSync {
+            val taskbarView: TaskbarView =
+                taskbarUnitTestRule.activityContext.dragLayer.findViewById(R.id.taskbar_view)
+            taskbarView.updateItems(
+                createHotseatItems(maxNumberOfTaskbarIcons - initialIconCount),
+                recentAppsController.shownTasks,
+            )
+        }
+
+        // Verify that recent task is shown (eventhough it exceeds max taskbar icons), and that
+        // the taskbar overflow view is not added for the single recent app.
+        assertThat(currentNumberOfTaskbarIcons).isEqualTo(maxNumberOfTaskbarIcons + 1)
+        assertThat(taskbarOverflowIconIndex).isEqualTo(-1)
+    }
+
+    @Test
+    @TaskbarMode(PINNED)
     fun testBubbleBarReducesTaskbarMaxNumIcons_pinned() {
         var initialMaxNumIconViews = maxNumberOfTaskbarIcons
         assertThat(initialMaxNumIconViews).isGreaterThan(0)
@@ -296,6 +346,20 @@
             }
         }
 
+    private val overflowItems: List<Int>
+        get() {
+            return getOnUiThread {
+                val overflowIcon =
+                    taskbarViewController.iconViews.firstOrNull { it is TaskbarOverflowView }
+
+                if (overflowIcon is TaskbarOverflowView) {
+                    overflowIcon.itemIds
+                } else {
+                    emptyList()
+                }
+            }
+        }
+
     /**
      * Adds enough running apps for taskbar to enter overflow of `targetOverflowSize`, and verifies
      * * max number of icons in the taskbar remains unchanged
@@ -318,6 +382,9 @@
         assertThat(currentNumberOfTaskbarIcons).isEqualTo(maxNumIconViews)
         assertThat(taskbarOverflowIconIndex)
             .isEqualTo(if (targetOverflowSize > 0) initialIconCount else -1)
+        if (targetOverflowSize > 0) {
+            assertThat(overflowItems).containsExactlyElementsIn(0..targetOverflowSize)
+        }
         return maxNumIconViews
     }
 }
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewTest.kt
index 0bb404b..44d31c4 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewTest.kt
@@ -154,4 +154,13 @@
         }
         assertThat(taskbarView).hasIconTypes(ALL_APPS, HOTSEAT, DIVIDER, RECENT)
     }
+
+    @Test
+    fun testUpdateItem_addHotseatItemAfterRecentsItem_hotseatItemBeforeDivider() {
+        runOnMainSync {
+            taskbarView.updateItems(emptyArray(), createRecents(1))
+            taskbarView.updateItems(createHotseatItems(1), createRecents(1))
+        }
+        assertThat(taskbarView).hasIconTypes(ALL_APPS, HOTSEAT, DIVIDER, RECENT)
+    }
 }
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewTestUtil.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewTestUtil.kt
index a6bdbb0..f2dcf77 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewTestUtil.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewTestUtil.kt
@@ -99,7 +99,7 @@
     /** Verifies that recents from [startIndex] have IDs that match [expectedIds] in order. */
     fun hasRecentsOrder(startIndex: Int, expectedIds: List<Int>) {
         val actualIds =
-            view.iconViews.slice(startIndex..<expectedIds.size).map {
+            view.iconViews.slice(startIndex..<startIndex + expectedIds.size).map {
                 assertThat(it.tag).isInstanceOf(GroupTask::class.java)
                 (it.tag as? GroupTask)?.task1?.key?.id
             }
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewWithLayoutTransitionTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewWithLayoutTransitionTest.kt
index 78d8e5d..4b6d5e5 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewWithLayoutTransitionTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewWithLayoutTransitionTest.kt
@@ -18,6 +18,7 @@
 
 import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.SetFlagsRule
+import android.view.View
 import com.android.launcher3.Flags.FLAG_TASKBAR_RECENTS_LAYOUT_TRANSITION
 import com.android.launcher3.R
 import com.android.launcher3.taskbar.TaskbarControllerTestUtil.runOnMainSync
@@ -33,6 +34,7 @@
 import com.android.launcher3.taskbar.rules.TaskbarWindowSandboxContext
 import com.android.launcher3.util.LauncherMultivalentJUnit
 import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices
+import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -49,6 +51,9 @@
 
     private lateinit var taskbarView: TaskbarView
 
+    private val iconViews: Array<View>
+        get() = taskbarView.iconViews
+
     @Before
     fun obtainView() {
         taskbarView = taskbarUnitTestRule.activityContext.dragLayer.findViewById(R.id.taskbar_view)
@@ -131,4 +136,93 @@
         }
         assertThat(taskbarView).hasIconTypes(RECENT, DIVIDER, HOTSEAT, ALL_APPS)
     }
+
+    @Test
+    fun testUpdateItems_addRecentsItem_viewAddedOnRight() {
+        runOnMainSync {
+            taskbarView.updateItems(emptyArray(), createRecents(1))
+            val prevIconViews = iconViews
+
+            val newRecents = createRecents(2)
+            taskbarView.updateItems(emptyArray(), newRecents)
+
+            assertThat(taskbarView).hasRecentsOrder(startIndex = 2, expectedIds = listOf(0, 1))
+            assertThat(iconViews[2]).isSameInstanceAs(prevIconViews[2])
+            assertThat(iconViews.last() in prevIconViews).isFalse()
+        }
+    }
+
+    @Test
+    @ForceRtl
+    fun testUpdateItems_rtl_addRecentsItem_viewAddedOnLeft() {
+        runOnMainSync {
+            taskbarView.updateItems(emptyArray(), createRecents(1))
+            val prevIconViews = iconViews
+
+            val newRecents = createRecents(2)
+            taskbarView.updateItems(emptyArray(), newRecents)
+
+            assertThat(taskbarView).hasRecentsOrder(startIndex = 0, expectedIds = listOf(1, 0))
+            assertThat(iconViews[1]).isSameInstanceAs(prevIconViews.first())
+            assertThat(iconViews.first() in prevIconViews).isFalse()
+        }
+    }
+
+    @Test
+    fun testUpdateItems_removeFirstRecentsItem_correspondingViewRemoved() {
+        runOnMainSync {
+            val recents = createRecents(2)
+            taskbarView.updateItems(emptyArray(), recents)
+
+            val expectedViewToRemove = iconViews[2]
+            assertThat(expectedViewToRemove.tag).isEqualTo(recents.first())
+
+            taskbarView.updateItems(emptyArray(), listOf(recents.last()))
+            assertThat(expectedViewToRemove in iconViews).isFalse()
+        }
+    }
+
+    @Test
+    fun testUpdateItems_removeLastRecentsItem_correspondingViewRemoved() {
+        runOnMainSync {
+            val recents = createRecents(2)
+            taskbarView.updateItems(emptyArray(), recents)
+
+            val expectedViewToRemove = iconViews[3]
+            assertThat(expectedViewToRemove.tag).isEqualTo(recents.last())
+
+            taskbarView.updateItems(emptyArray(), listOf(recents.first()))
+            assertThat(expectedViewToRemove in iconViews).isFalse()
+        }
+    }
+
+    @Test
+    @ForceRtl
+    fun testUpdateItems_rtl_removeFirstRecentsItem_correspondingViewRemoved() {
+        runOnMainSync {
+            val recents = createRecents(2)
+            taskbarView.updateItems(emptyArray(), recents)
+
+            val expectedViewToRemove = iconViews[1]
+            assertThat(expectedViewToRemove.tag).isEqualTo(recents.first())
+
+            taskbarView.updateItems(emptyArray(), listOf(recents.last()))
+            assertThat(expectedViewToRemove in iconViews).isFalse()
+        }
+    }
+
+    @Test
+    @ForceRtl
+    fun testUpdateItems_rtl_removeLastRecentsItem_correspondingViewRemoved() {
+        runOnMainSync {
+            val recents = createRecents(2)
+            taskbarView.updateItems(emptyArray(), recents)
+
+            val expectedViewToRemove = iconViews[0]
+            assertThat(expectedViewToRemove.tag).isEqualTo(recents.last())
+
+            taskbarView.updateItems(emptyArray(), listOf(recents.first()))
+            assertThat(expectedViewToRemove in iconViews).isFalse()
+        }
+    }
 }
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt
index 8c51216..0b94dfd 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt
@@ -25,6 +25,7 @@
 import com.android.launcher3.FakeLauncherPrefs
 import com.android.launcher3.LauncherPrefs
 import com.android.launcher3.dagger.LauncherAppComponent
+import com.android.launcher3.dagger.LauncherAppModule
 import com.android.launcher3.dagger.LauncherAppSingleton
 import com.android.launcher3.util.MainThreadInitializedObject.ObjectSandbox
 import com.android.launcher3.util.SandboxApplication
@@ -116,7 +117,7 @@
 }
 
 @LauncherAppSingleton
-@Component
+@Component(modules = [LauncherAppModule::class])
 interface TaskbarSandboxComponent : LauncherAppComponent {
     @Component.Builder
     interface Builder : LauncherAppComponent.Builder {
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java
index 970bdec..c334552 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java
@@ -30,7 +30,9 @@
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import android.animation.ValueAnimator;
@@ -50,7 +52,6 @@
 import android.view.ViewTreeObserver;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.launcher3.DeviceProfile;
@@ -58,14 +59,18 @@
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.statemanager.BaseState;
 import com.android.launcher3.statemanager.StatefulContainer;
+import com.android.launcher3.util.LauncherModelHelper;
+import com.android.launcher3.util.MSDLPlayerWrapper;
 import com.android.launcher3.util.SystemUiController;
-import com.android.quickstep.fallback.window.RecentsWindowManager;
 import com.android.quickstep.util.ContextInitListener;
+import com.android.quickstep.util.MotionPauseDetector;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.RecentsViewContainer;
 import com.android.systemui.shared.Flags;
 import com.android.systemui.shared.system.InputConsumerController;
 
+import com.google.android.msdl.data.model.MSDLToken;
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -84,8 +89,9 @@
         SWIPE_HANDLER extends AbsSwipeUpHandler<RECENTS_CONTAINER, RECENTS_VIEW, STATE_TYPE>,
         CONTAINER_INTERFACE extends BaseContainerInterface<STATE_TYPE, RECENTS_CONTAINER>> {
 
-    protected final Context mContext =
-            InstrumentationRegistry.getInstrumentation().getTargetContext();
+    protected final LauncherModelHelper mLauncherModelHelper = new LauncherModelHelper();
+    protected final LauncherModelHelper.SandboxModelContext mContext =
+            mLauncherModelHelper.sandboxContext;
     protected final InputConsumerController mInputConsumerController =
             InputConsumerController.getRecentsAnimationInputConsumer();
     protected final ActivityManager.RunningTaskInfo mRunningTaskInfo =
@@ -123,6 +129,7 @@
     @Mock protected LauncherRootView mRootView;
     @Mock protected SystemUiController mSystemUiController;
     @Mock protected GestureState mGestureState;
+    @Mock protected MSDLPlayerWrapper mMSDLPlayerWrapper;
 
     @Rule
     public final MockitoRule mMockitoRule = MockitoJUnit.rule();
@@ -173,7 +180,7 @@
 
     @Before
     public void setUpRecentsContainer() {
-        mTaskAnimationManager = new TaskAnimationManager(mContext, getRecentsWindowManager());
+        mTaskAnimationManager = new TaskAnimationManager(mContext, mRecentsAnimationDeviceState);
         RecentsViewContainer recentsContainer = getRecentsContainer();
         RECENTS_VIEW recentsView = getRecentsView();
 
@@ -304,6 +311,17 @@
         });
     }
 
+    @Test
+    @EnableFlags(com.android.launcher3.Flags.FLAG_MSDL_FEEDBACK)
+    public void onMotionPauseDetected_playsSwipeThresholdToken() {
+        SWIPE_HANDLER handler = createSwipeHandler();
+        MotionPauseDetector.OnMotionPauseListener listener = handler.getMotionPauseListener();
+        listener.onMotionPauseDetected();
+
+        verify(mMSDLPlayerWrapper, times(1)).playToken(eq(MSDLToken.SWIPE_THRESHOLD_INDICATOR));
+        verifyNoMoreInteractions(mMSDLPlayerWrapper);
+    }
+
     /**
      * Verifies that RecentsAnimationController#finish() is called, and captures and runs any
      * callback that was passed to it. This ensures that STATE_CURRENT_TASK_FINISHED is correctly
@@ -364,11 +382,6 @@
         return createSwipeHandler(SystemClock.uptimeMillis(), false);
     }
 
-    @Nullable
-    protected RecentsWindowManager getRecentsWindowManager() {
-        return null;
-    }
-
     @NonNull
     protected abstract SWIPE_HANDLER createSwipeHandler(
             long touchTimeMs, boolean continuingLastGesture);
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/DisplayModelTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/DisplayModelTest.kt
new file mode 100644
index 0000000..a939e84
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/DisplayModelTest.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep
+
+import android.content.Context
+import android.view.Display
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertNull
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DisplayModelTest {
+    private val context: Context = ApplicationProvider.getApplicationContext()
+
+    class TestableResource : DisplayModel.DisplayResource() {
+        var isCleanupCalled = false
+
+        override fun cleanup() {
+            isCleanupCalled = true
+        }
+    }
+
+    private val testableDisplayModel =
+        object : DisplayModel<TestableResource>(context) {
+            override fun createDisplayResource(displayId: Int) {
+                displayResourceArray.put(displayId, TestableResource())
+            }
+        }
+
+    @Test
+    fun testCreate() {
+        testableDisplayModel.createDisplayResource(Display.DEFAULT_DISPLAY)
+        val resource = testableDisplayModel.getDisplayResource(Display.DEFAULT_DISPLAY)
+        assertNotNull(resource)
+    }
+
+    @Test
+    fun testCleanAndDelete() {
+        testableDisplayModel.createDisplayResource(Display.DEFAULT_DISPLAY)
+        val resource = testableDisplayModel.getDisplayResource(Display.DEFAULT_DISPLAY)!!
+        assertNotNull(resource)
+        testableDisplayModel.deleteDisplayResource(Display.DEFAULT_DISPLAY)
+        assert(resource.isCleanupCalled)
+        assertNull(testableDisplayModel.getDisplayResource(Display.DEFAULT_DISPLAY))
+    }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/FallbackSwipeHandlerTestCase.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/FallbackSwipeHandlerTestCase.java
index 88197e5..d4eb8e2 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/FallbackSwipeHandlerTestCase.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/FallbackSwipeHandlerTestCase.java
@@ -49,7 +49,8 @@
                 mGestureState,
                 touchTimeMs,
                 continuingLastGesture,
-                mInputConsumerController);
+                mInputConsumerController,
+                mMSDLPlayerWrapper);
     }
 
     @NonNull
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2Test.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2Test.kt
index 32b5b85..50ba55c 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2Test.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2Test.kt
@@ -21,9 +21,11 @@
 import androidx.test.filters.SmallTest
 import com.android.launcher3.R
 import com.android.launcher3.dagger.LauncherAppComponent
+import com.android.launcher3.dagger.LauncherAppModule
 import com.android.launcher3.dagger.LauncherAppSingleton
 import com.android.launcher3.util.LauncherModelHelper
-import com.android.quickstep.dagger.QuickStepModule
+import com.android.launcher3.util.MSDLPlayerWrapper
+import com.android.quickstep.fallback.window.RecentsDisplayModel
 import com.android.systemui.contextualeducation.GestureType
 import com.android.systemui.shared.system.InputConsumerController
 import dagger.BindsInstance
@@ -51,6 +53,10 @@
 
     @Mock private lateinit var systemUiProxy: SystemUiProxy
 
+    @Mock private lateinit var recentsDisplayModel: RecentsDisplayModel
+
+    @Mock private lateinit var msdlPlayerWrapper: MSDLPlayerWrapper
+
     private lateinit var underTest: LauncherSwipeHandlerV2
 
     @get:Rule val mockitoRule = MockitoJUnit.rule()
@@ -64,10 +70,14 @@
     @Before
     fun setup() {
         sandboxContext.initDaggerComponent(
-            DaggerTestComponent.builder().bindSystemUiProxy(systemUiProxy)
+            DaggerTestComponent.builder()
+                .bindSystemUiProxy(systemUiProxy)
+                .bindRecentsDisplayModel(recentsDisplayModel)
         )
+
         val deviceState = mock(RecentsAnimationDeviceState::class.java)
         whenever(deviceState.rotationTouchHelper).thenReturn(mock(RotationTouchHelper::class.java))
+
         gestureState = spy(GestureState(OverviewComponentObserver(sandboxContext, deviceState), 0))
 
         underTest =
@@ -79,6 +89,7 @@
                 0,
                 false,
                 inputConsumerController,
+                msdlPlayerWrapper,
             )
         underTest.onGestureStarted(/* isLikelyToStartNewTask= */ false)
     }
@@ -106,12 +117,14 @@
 }
 
 @LauncherAppSingleton
-@Component(modules = [QuickStepModule::class])
+@Component(modules = [LauncherAppModule::class])
 interface TestComponent : LauncherAppComponent {
     @Component.Builder
     interface Builder : LauncherAppComponent.Builder {
         @BindsInstance fun bindSystemUiProxy(proxy: SystemUiProxy): Builder
 
+        @BindsInstance fun bindRecentsDisplayModel(model: RecentsDisplayModel): Builder
+
         override fun build(): TestComponent
     }
 }
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2TestCase.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2TestCase.java
index ec1dc8b..fc6acfd 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2TestCase.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2TestCase.java
@@ -78,7 +78,8 @@
                 mGestureState,
                 touchTimeMs,
                 continuingLastGesture,
-                mInputConsumerController);
+                mInputConsumerController,
+                mMSDLPlayerWrapper);
     }
 
     @NonNull
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsWindowSwipeHandlerTestCase.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsWindowSwipeHandlerTestCase.java
index 1bdf273..40fefae 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsWindowSwipeHandlerTestCase.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsWindowSwipeHandlerTestCase.java
@@ -17,19 +17,26 @@
 package com.android.quickstep;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 import androidx.test.filters.SmallTest;
 
+import com.android.launcher3.dagger.LauncherAppComponent;
+import com.android.launcher3.dagger.LauncherAppSingleton;
 import com.android.launcher3.util.LauncherMultivalentJUnit;
+import com.android.quickstep.dagger.QuickStepModule;
 import com.android.quickstep.fallback.FallbackRecentsView;
 import com.android.quickstep.fallback.RecentsState;
+import com.android.quickstep.fallback.window.RecentsDisplayModel;
 import com.android.quickstep.fallback.window.RecentsWindowManager;
 import com.android.quickstep.fallback.window.RecentsWindowSwipeHandler;
 import com.android.quickstep.views.RecentsViewContainer;
 
+import org.junit.Before;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 
+import dagger.BindsInstance;
+import dagger.Component;
+
 @SmallTest
 @RunWith(LauncherMultivalentJUnit.class)
 public class RecentsWindowSwipeHandlerTestCase extends AbsSwipeUpHandlerTestCase<
@@ -39,8 +46,15 @@
         RecentsWindowSwipeHandler,
         FallbackWindowInterface> {
 
-    @Mock private RecentsWindowManager mRecentsWindowManager;
+    @Mock private RecentsDisplayModel mRecentsDisplayModel;
     @Mock private FallbackRecentsView<RecentsWindowManager> mRecentsView;
+    @Mock private RecentsWindowManager mRecentsWindowManager;
+
+    @Before
+    public void setRecentsDisplayModel() {
+        mContext.initDaggerComponent(DaggerRecentsWindowSwipeHandlerTestCase_TestComponent.builder()
+                .bindRecentsDisplayModel(mRecentsDisplayModel));
+    }
 
     @NonNull
     @Override
@@ -54,13 +68,7 @@
                 touchTimeMs,
                 continuingLastGesture,
                 mInputConsumerController,
-                mRecentsWindowManager);
-    }
-
-    @Nullable
-    @Override
-    protected RecentsWindowManager getRecentsWindowManager() {
-        return mRecentsWindowManager;
+                mMSDLPlayerWrapper);
     }
 
     @NonNull
@@ -74,4 +82,14 @@
     protected FallbackRecentsView<RecentsWindowManager> getRecentsView() {
         return mRecentsView;
     }
+
+    @LauncherAppSingleton
+    @Component(modules = { QuickStepModule.class })
+    interface TestComponent extends LauncherAppComponent {
+        @Component.Builder
+        interface Builder extends LauncherAppComponent.Builder {
+            @BindsInstance Builder bindRecentsDisplayModel(RecentsDisplayModel model);
+            @Override LauncherAppComponent build();
+        }
+    }
 }
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt
index d6688d6..1c9ce0b 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt
@@ -49,6 +49,9 @@
     override fun getThumbnailById(taskId: Int): Flow<ThumbnailData?> =
         getTaskDataById(taskId).map { it?.thumbnail }
 
+    override fun getCurrentThumbnailById(taskId: Int): ThumbnailData? =
+        tasks.value.firstOrNull { it.key.id == taskId }?.thumbnail
+
     override fun setVisibleTasks(visibleTaskIdList: Set<Int>) {
         visibleTasks.value = visibleTaskIdList
         tasks.value =
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt
index 357df6e..e0e7f28 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt
@@ -87,6 +87,48 @@
         }
 
     @Test
+    fun getThumbnailByIdReturnsNullWithNoLoadedThumbnails() =
+        testScope.runTest {
+            recentsModel.seedTasks(defaultTaskList)
+            systemUnderTest.getAllTaskData(forceRefresh = true)
+
+            assertThat(systemUnderTest.getThumbnailById(1).first()).isNull()
+        }
+
+    @Test
+    fun getCurrentThumbnailByIdReturnsNullWithNoLoadedThumbnails() =
+        testScope.runTest {
+            recentsModel.seedTasks(defaultTaskList)
+            systemUnderTest.getAllTaskData(forceRefresh = true)
+
+            assertThat(systemUnderTest.getCurrentThumbnailById(1)).isNull()
+        }
+
+    @Test
+    fun getThumbnailByIdReturnsThumbnailWithLoadedThumbnails() =
+        testScope.runTest {
+            recentsModel.seedTasks(defaultTaskList)
+            systemUnderTest.getAllTaskData(forceRefresh = true)
+            val bitmap1 = taskThumbnailDataSource.taskIdToBitmap[1]
+
+            systemUnderTest.setVisibleTasks(setOf(1))
+
+            assertThat(systemUnderTest.getThumbnailById(1).first()!!.thumbnail).isEqualTo(bitmap1)
+        }
+
+    @Test
+    fun getCurrentThumbnailByIdReturnsThumbnailWithLoadedThumbnails() =
+        testScope.runTest {
+            recentsModel.seedTasks(defaultTaskList)
+            systemUnderTest.getAllTaskData(forceRefresh = true)
+            val bitmap1 = taskThumbnailDataSource.taskIdToBitmap[1]
+
+            systemUnderTest.setVisibleTasks(setOf(1))
+
+            assertThat(systemUnderTest.getCurrentThumbnailById(1)?.thumbnail).isEqualTo(bitmap1)
+        }
+
+    @Test
     fun setVisibleTasksPopulatesThumbnails() =
         testScope.runTest {
             recentsModel.seedTasks(defaultTaskList)
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/window/RecentsDisplayModelTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/window/RecentsDisplayModelTest.kt
new file mode 100644
index 0000000..d2aa6ac
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/window/RecentsDisplayModelTest.kt
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.recents.window
+
+import android.graphics.Point
+import android.hardware.display.DisplayManager
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import android.view.Display
+import android.view.DisplayAdjustments
+import android.view.Surface
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.Flags.FLAG_ENABLE_FALLBACK_OVERVIEW_IN_WINDOW
+import com.android.launcher3.Flags.FLAG_ENABLE_LAUNCHER_OVERVIEW_IN_WINDOW
+import com.android.launcher3.dagger.LauncherAppComponent
+import com.android.launcher3.dagger.LauncherAppModule
+import com.android.launcher3.dagger.LauncherAppSingleton
+import com.android.launcher3.util.LauncherModelHelper
+import com.android.launcher3.util.window.CachedDisplayInfo
+import com.android.quickstep.fallback.window.RecentsDisplayModel
+import dagger.BindsInstance
+import dagger.Component
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(FLAG_ENABLE_LAUNCHER_OVERVIEW_IN_WINDOW, FLAG_ENABLE_FALLBACK_OVERVIEW_IN_WINDOW)
+class RecentsDisplayModelTest {
+    @get:Rule val setFlagsRule = SetFlagsRule()
+
+    // initiate dagger components for injection
+    private val launcherModelHelper = LauncherModelHelper()
+    private val context = spy(launcherModelHelper.sandboxContext)
+    private val displayManager: DisplayManager = context.spyService(DisplayManager::class.java)
+    private val display: Display = mock()
+
+    private lateinit var recentsDisplayModel: RecentsDisplayModel
+
+    private val width = 2208
+    private val height = 1840
+
+    @Before
+    fun setup() {
+        // Mock display
+        val displayInfo = CachedDisplayInfo(Point(width, height), Surface.ROTATION_0)
+        whenever(display.rotation).thenReturn(displayInfo.rotation)
+        whenever(display.displayAdjustments).thenReturn(DisplayAdjustments())
+        whenever(context.display).thenReturn(display)
+
+        // Mock displayManager
+        whenever(displayManager.getDisplay(anyInt())).thenReturn(display)
+
+        runOnMainSync { recentsDisplayModel = RecentsDisplayModel.INSTANCE.get(context) }
+        context.initDaggerComponent(
+            DaggerRecentsDisplayModelComponent.builder()
+                .bindRecentsDisplayModel(recentsDisplayModel)
+        )
+    }
+
+    @Test
+    fun testEnsureSingleton() {
+        val recentsDisplayModel2 = RecentsDisplayModel.INSTANCE.get(context)
+        assert(recentsDisplayModel == recentsDisplayModel2)
+    }
+
+    @Test
+    fun testDefaultDisplayCreation() {
+        Assert.assertNotNull(recentsDisplayModel.getRecentsWindowManager(Display.DEFAULT_DISPLAY))
+        Assert.assertNotNull(
+            recentsDisplayModel.getFallbackWindowInterface(Display.DEFAULT_DISPLAY)
+        )
+    }
+
+    @Test
+    fun testCreateSeparateInstances() {
+        val display = Display.DEFAULT_DISPLAY + 1
+        runOnMainSync { recentsDisplayModel.createDisplayResource(display) }
+
+        val defaultManager = recentsDisplayModel.getRecentsWindowManager(Display.DEFAULT_DISPLAY)
+        val secondaryManager = recentsDisplayModel.getRecentsWindowManager(display)
+        Assert.assertNotSame(defaultManager, secondaryManager)
+
+        val defaultInterface =
+            recentsDisplayModel.getFallbackWindowInterface(Display.DEFAULT_DISPLAY)
+        val secondInterface = recentsDisplayModel.getFallbackWindowInterface(display)
+        Assert.assertNotSame(defaultInterface, secondInterface)
+    }
+
+    @Test
+    fun testDestroy() {
+        Assert.assertNotNull(recentsDisplayModel.getRecentsWindowManager(Display.DEFAULT_DISPLAY))
+        Assert.assertNotNull(
+            recentsDisplayModel.getFallbackWindowInterface(Display.DEFAULT_DISPLAY)
+        )
+        recentsDisplayModel.destroy()
+        Assert.assertNull(recentsDisplayModel.getRecentsWindowManager(Display.DEFAULT_DISPLAY))
+        Assert.assertNull(recentsDisplayModel.getFallbackWindowInterface(Display.DEFAULT_DISPLAY))
+    }
+
+    private fun runOnMainSync(f: Runnable) {
+        InstrumentationRegistry.getInstrumentation().runOnMainSync { f.run() }
+    }
+}
+
+@LauncherAppSingleton
+@Component(modules = [LauncherAppModule::class])
+interface RecentsDisplayModelComponent : LauncherAppComponent {
+    @Component.Builder
+    interface Builder : LauncherAppComponent.Builder {
+        @BindsInstance fun bindRecentsDisplayModel(model: RecentsDisplayModel): Builder
+
+        override fun build(): RecentsDisplayModelComponent
+    }
+}
diff --git a/quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java b/quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java
index c53c177..1c4ce74 100644
--- a/quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java
+++ b/quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java
@@ -42,13 +42,13 @@
 import android.content.ComponentName;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.LauncherApps;
+import android.os.Process;
 import android.os.UserHandle;
 import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.text.TextUtils;
 
-import androidx.test.core.content.pm.ApplicationInfoBuilder;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
@@ -122,10 +122,10 @@
         mLauncherApps = mModelHelper.sandboxContext.spyService(LauncherApps.class);
         doAnswer(i -> {
             String pkg = i.getArgument(0);
-            ApplicationInfo applicationInfo = ApplicationInfoBuilder.newBuilder()
-                    .setPackageName(pkg)
-                    .setName("App " + pkg)
-                    .build();
+            ApplicationInfo applicationInfo = new ApplicationInfo();
+            applicationInfo.packageName = pkg;
+            applicationInfo.name = "App " + pkg;
+            applicationInfo.uid = Process.myUid();
             applicationInfo.category = CATEGORY_PRODUCTIVITY;
             applicationInfo.flags = FLAG_INSTALLED;
             return applicationInfo;
diff --git a/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java b/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java
index 6a7b6f8..f57c35f 100644
--- a/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java
+++ b/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java
@@ -20,18 +20,28 @@
 
 import android.os.SystemProperties;
 
+import androidx.annotation.NonNull;
 import androidx.test.uiautomator.By;
 import androidx.test.uiautomator.Until;
 
 import com.android.launcher3.tapl.LaunchedAppState;
+import com.android.launcher3.tapl.TestHelpers;
 import com.android.launcher3.ui.AbstractLauncherUiTest;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
 import com.android.launcher3.util.TestUtil;
+import com.android.launcher3.util.Wait;
+import com.android.quickstep.fallback.RecentsState;
+import com.android.quickstep.fallback.window.RecentsWindowManager;
 import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.RecentsViewContainer;
 
 import org.junit.rules.RuleChain;
 import org.junit.rules.TestRule;
 
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
 /**
  * Base class for all instrumentation tests that deal with Quickstep.
  */
@@ -54,6 +64,55 @@
         }
     }
 
+    // Cannot be used in TaplTests between a Tapl call injecting a gesture and a tapl call
+    // expecting the results of that gesture because the wait can hide flakeness.
+    protected void waitForRecentsWindowState(String message, Supplier<RecentsState> state) {
+        waitForRecentsWindowCondition(message, recentsWindow ->
+                recentsWindow.getStateManager().getCurrentStableState() == state.get());
+    }
+
+    // Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide
+    // flakiness.
+    protected void waitForRecentsWindowCondition(String
+            message, Function<RecentsWindowManager, Boolean> condition) {
+        waitForRecentsWindowCondition(message, condition, TestUtil.DEFAULT_UI_TIMEOUT);
+    }
+
+    protected <T> T getFromRecentsWindowManager(Function<RecentsWindowManager, T> f) {
+        if (!TestHelpers.isInLauncherProcess()) return null;
+        return getOnUiThread(
+                () -> f.apply(RecentsWindowManager.getRecentsWindowTracker().getCreatedContext()));
+    }
+
+    protected void executeOnRecentsWindow(Consumer<RecentsWindowManager> f) {
+        getFromRecentsWindowManager(recentsWindow -> {
+            f.accept(recentsWindow);
+            return null;
+        });
+    }
+
+    protected void executeOnRecentsViewContainerInTearDown(
+            @NonNull Consumer<RecentsViewContainer> f) {
+        executeOnRecentsWindow(container -> {
+            if (container != null) f.accept(container);
+        });
+    }
+
+    // Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide
+    // flakiness.
+    protected void waitForRecentsWindowCondition(
+            String message, Function<RecentsWindowManager, Boolean> condition, long timeout) {
+        verifyKeyguardInvisible();
+        if (!TestHelpers.isInLauncherProcess()) return;
+        Wait.atMost(message, () -> getFromRecentsWindowManager(condition), mLauncher, timeout);
+    }
+
+    protected boolean isInRecentsWindowState(Supplier<RecentsState> state) {
+        if (!TestHelpers.isInLauncherProcess()) return true;
+        return getFromRecentsWindowManager(
+                recentsWindow -> recentsWindow.getStateManager().getState() == state.get());
+    }
+
     protected void assertTestActivityIsRunning(int activityNumber, String message) {
         assertTrue(message, mDevice.wait(
                 Until.hasObject(By.pkg(getAppPackageName()).text("TestActivity" + activityNumber)),
diff --git a/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java b/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java
index 5b46dc8..f923142 100644
--- a/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java
+++ b/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java
@@ -101,7 +101,7 @@
     }
 
     private TaskView getLatestTask(Launcher launcher) {
-        return launcher.<RecentsView>getOverviewPanel().getTaskViewAt(0);
+        return launcher.<RecentsView>getOverviewPanel().getFirstTaskView();
     }
 
     private void runWithShellPermission(Runnable action) {
diff --git a/quickstep/tests/src/com/android/quickstep/InputConsumerUtilsTest.java b/quickstep/tests/src/com/android/quickstep/InputConsumerUtilsTest.java
index 5dc6932..f522a2c 100644
--- a/quickstep/tests/src/com/android/quickstep/InputConsumerUtilsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/InputConsumerUtilsTest.java
@@ -56,7 +56,6 @@
 import com.android.launcher3.util.LockedUserState;
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.views.BaseDragLayer;
-import com.android.quickstep.fallback.window.RecentsWindowManager;
 import com.android.quickstep.inputconsumers.AccessibilityInputConsumer;
 import com.android.quickstep.inputconsumers.BubbleBarInputConsumer;
 import com.android.quickstep.inputconsumers.DeviceLockedInputConsumer;
@@ -96,10 +95,9 @@
 
     @NonNull private final MainThreadInitializedObject.SandboxContext mContext =
             new MainThreadInitializedObject.SandboxContext(getApplicationContext());
-    @NonNull private final TaskAnimationManager mTaskAnimationManager = new TaskAnimationManager(
-            mContext, mock(RecentsWindowManager.class));
     @NonNull private final InputMonitorCompat mInputMonitorCompat = new InputMonitorCompat("", 0);
 
+    private TaskAnimationManager mTaskAnimationManager;
     private InputChannelCompat.InputEventReceiver mInputEventReceiver;
     @Nullable private ResetGestureInputConsumer mResetGestureInputConsumer;
     @NonNull private Function<GestureState, AnimatedFloat> mSwipeUpProxyProvider = (state) -> null;
@@ -121,6 +119,12 @@
     public final MockitoRule mMockitoRule = MockitoJUnit.rule();
 
     @Before
+    public void setupTaskAnimationManager() {
+        mTaskAnimationManager = new TaskAnimationManager(
+                mContext, mDeviceState);
+    }
+
+    @Before
     public void setupMainThreadInitializedObjects() {
         mContext.putObject(LockedUserState.INSTANCE, mLockedUserState);
     }
@@ -184,6 +188,7 @@
 
     @Before
     public void setupDeviceState() {
+        when(mDeviceState.getDisplayId()).thenReturn(0);
         when(mDeviceState.canStartTrackpadGesture()).thenReturn(true);
         when(mDeviceState.canStartSystemGesture()).thenReturn(true);
         when(mDeviceState.isFullyGesturalNavMode()).thenReturn(true);
diff --git a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
index 77f4c05..c713c3d 100644
--- a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
+++ b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
@@ -16,7 +16,7 @@
 
 package com.android.quickstep;
 
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
 import static com.android.quickstep.NavigationModeSwitchRule.Mode.ALL;
 import static com.android.quickstep.NavigationModeSwitchRule.Mode.THREE_BUTTON;
@@ -26,6 +26,7 @@
 
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.os.Process;
 import android.util.Log;
 
 import androidx.test.uiautomator.UiDevice;
@@ -153,7 +154,9 @@
 
         Log.d(TAG, "setActiveOverlay: " + overlayPackage + "...");
         UiDevice.getInstance(getInstrumentation()).executeShellCommand(
-                "cmd overlay enable-exclusive --category " + overlayPackage);
+                String.format("cmd overlay enable-exclusive --user %d --category %s",
+                        Process.myUserHandle().getIdentifier(),
+                        overlayPackage));
 
         if (currentSysUiNavigationMode() != expectedMode) {
             final CountDownLatch latch = new CountDownLatch(1);
diff --git a/quickstep/tests/src/com/android/quickstep/TaplPrivateSpaceTest.java b/quickstep/tests/src/com/android/quickstep/TaplPrivateSpaceTest.java
index b15b78e..e065dba 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplPrivateSpaceTest.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplPrivateSpaceTest.java
@@ -23,6 +23,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeFalse;
 
+import android.os.Process;
 import android.util.Log;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -74,8 +75,9 @@
     }
 
     private void createAndStartPrivateProfileUser() {
-        String createUserOutput = executeShellCommand("pm create-user --profileOf 0 --user-type "
-                + "android.os.usertype.profile.PRIVATE " + PRIVATE_PROFILE_NAME);
+        int myUserId = Process.myUserHandle().getIdentifier();
+        String createUserOutput = executeShellCommand("pm create-user --profileOf " + myUserId
+                + " --user-type android.os.usertype.profile.PRIVATE " + PRIVATE_PROFILE_NAME);
         updatePrivateProfileSetupSuccessful("pm create-user", createUserOutput);
         String[] tokens = createUserOutput.split("\\s+");
         mProfileUserId = Integer.parseInt(tokens[tokens.length - 1]);
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index f1fe2d2..ec6a9c3 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -16,6 +16,7 @@
 
 package com.android.quickstep;
 
+import static com.android.launcher3.Flags.enableLauncherOverviewInWindow;
 import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
 import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
 import static com.android.quickstep.TaskbarModeSwitchRule.Mode.TRANSIENT;
@@ -30,13 +31,13 @@
 import android.content.Intent;
 import android.content.res.Configuration;
 
+import androidx.annotation.NonNull;
 import androidx.test.filters.LargeTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 import androidx.test.uiautomator.By;
 import androidx.test.uiautomator.Until;
 
-import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.tapl.BaseOverview;
 import com.android.launcher3.tapl.LaunchedAppState;
@@ -53,7 +54,9 @@
 import com.android.launcher3.util.rule.TestStabilityRule;
 import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
 import com.android.quickstep.TaskbarModeSwitchRule.TaskbarModeSwitch;
+import com.android.quickstep.fallback.RecentsState;
 import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.RecentsViewContainer;
 
 import org.junit.After;
 import org.junit.Before;
@@ -61,6 +64,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.function.Consumer;
+
 @LargeTest
 @RunWith(AndroidJUnit4.class)
 public class TaplTestsQuickstep extends AbstractQuickStepTest {
@@ -70,19 +75,33 @@
     private static final String READ_DEVICE_CONFIG_PERMISSION =
             "android.permission.READ_DEVICE_CONFIG";
 
+    private enum ExpectedState {
+
+        HOME(LauncherState.NORMAL, RecentsState.HOME),
+        OVERVIEW(LauncherState.OVERVIEW, RecentsState.DEFAULT);
+
+        private final LauncherState mLauncherState;
+        private final RecentsState mRecentsState;
+
+        ExpectedState(LauncherState launcherState, RecentsState recentsState) {
+            this.mLauncherState = launcherState;
+            this.mRecentsState = recentsState;
+        }
+    }
+
     @Before
     public void setUp() throws Exception {
         super.setUp();
-        executeOnLauncher(launcher -> {
-            RecentsView recentsView = launcher.getOverviewPanel();
+        executeOnRecentsViewContainer(container -> {
+            RecentsView recentsView = container.getOverviewPanel();
             recentsView.getPagedViewOrientedState().forceAllowRotationForTesting(true);
         });
     }
 
     @After
     public void tearDown() {
-        executeOnLauncherInTearDown(launcher -> {
-            RecentsView recentsView = launcher.getOverviewPanel();
+        executeOnRecentsViewContainerInTearDown(container -> {
+            RecentsView recentsView = container.getOverviewPanel();
             recentsView.getPagedViewOrientedState().forceAllowRotationForTesting(false);
         });
     }
@@ -117,28 +136,27 @@
         startTestAppsWithCheck();
         // mLauncher.pressHome() also tests an important case of pressing home while in background.
         Overview overview = mLauncher.goHome().switchToOverview();
-        assertTrue("Launcher internal state didn't switch to Overview",
-                isInState(() -> LauncherState.OVERVIEW));
-        executeOnLauncher(
-                launcher -> assertTrue("Don't have at least 3 tasks", getTaskCount(launcher) >= 3));
+        assertIsInState(
+                "Launcher internal state didn't switch to Overview", ExpectedState.OVERVIEW);
+        executeOnRecentsViewContainer(container -> assertTrue(
+                "Don't have at least 3 tasks", getTaskCount(container) >= 3));
 
         // Test flinging forward and backward.
-        executeOnLauncher(launcher -> assertEquals("Current task in Overview is not 0",
-                0, getCurrentOverviewPage(launcher)));
+        executeOnRecentsViewContainer(container -> assertEquals("Current task in Overview is not 0",
+                0, getCurrentOverviewPage(container)));
 
         overview.flingForward();
-        assertTrue("Launcher internal state is not Overview",
-                isInState(() -> LauncherState.OVERVIEW));
+        assertIsInState("Launcher internal state is not Overview", ExpectedState.OVERVIEW);
         final Integer currentTaskAfterFlingForward = getFromLauncher(
                 launcher -> getCurrentOverviewPage(launcher));
-        executeOnLauncher(launcher -> assertTrue("Current task in Overview is still 0",
-                currentTaskAfterFlingForward > 0));
+        executeOnRecentsViewContainer(container -> assertTrue(
+                "Current task in Overview is still 0", currentTaskAfterFlingForward > 0));
 
         overview.flingBackward();
-        assertTrue("Launcher internal state is not Overview",
-                isInState(() -> LauncherState.OVERVIEW));
-        executeOnLauncher(launcher -> assertTrue("Flinging back in Overview did nothing",
-                getCurrentOverviewPage(launcher) < currentTaskAfterFlingForward));
+        assertIsInState("Launcher internal state is not Overview", ExpectedState.OVERVIEW);
+        executeOnRecentsViewContainer(container -> assertTrue(
+                "Flinging back in Overview did nothing",
+                getCurrentOverviewPage(container) < currentTaskAfterFlingForward));
 
         // Test opening a task.
         OverviewTask task = mLauncher.goHome().switchToOverview().getCurrentTask();
@@ -154,23 +172,22 @@
 
         // Test dismissing a task.
         overview = mLauncher.goHome().switchToOverview();
-        assertTrue("Launcher internal state didn't switch to Overview",
-                isInState(() -> LauncherState.OVERVIEW));
+        assertIsInState(
+                "Launcher internal state didn't switch to Overview", ExpectedState.OVERVIEW);
         final Integer numTasks = getFromLauncher(launcher -> getTaskCount(launcher));
         task = overview.getCurrentTask();
         assertNotNull("overview.getCurrentTask() returned null (2)", task);
         task.dismiss();
-        executeOnLauncher(
-                launcher -> assertEquals("Dismissing a task didn't remove 1 task from Overview",
-                        numTasks - 1, getTaskCount(launcher)));
+        executeOnRecentsViewContainer(
+                container -> assertEquals("Dismissing a task didn't remove 1 task from Overview",
+                        numTasks - 1, getTaskCount(container)));
 
         // Test dismissing all tasks.
         mLauncher.goHome().switchToOverview().dismissAllTasks();
-        assertTrue("Launcher internal state is not Home",
-                isInState(() -> LauncherState.NORMAL));
-        executeOnLauncher(
-                launcher -> assertEquals("Still have tasks after dismissing all",
-                        0, getTaskCount(launcher)));
+        assertIsInState("Launcher internal state is not Home", ExpectedState.HOME);
+        executeOnRecentsViewContainer(
+                container -> assertEquals("Still have tasks after dismissing all",
+                        0, getTaskCount(container)));
     }
 
     /**
@@ -192,12 +209,10 @@
     public void testDismissOverviewWithEscKey() throws Exception {
         startTestAppsWithCheck();
         final Overview overview = mLauncher.goHome().switchToOverview();
-        assertTrue("Launcher internal state is not Overview",
-                isInState(() -> LauncherState.OVERVIEW));
+        assertIsInState("Launcher internal state is not Overview", ExpectedState.OVERVIEW);
 
         overview.dismissByEscKey();
-        assertTrue("Launcher internal state is not Home",
-                isInState(() -> LauncherState.NORMAL));
+        assertIsInState("Launcher internal state is not Home", ExpectedState.HOME);
     }
 
     @Test
@@ -218,11 +233,9 @@
 
         selectModeButtons.dismissByEscKey();
 
-        assertTrue("Launcher internal state is not Overview",
-                isInState(() -> LauncherState.OVERVIEW));
+        assertIsInState("Launcher internal state is not Overview", ExpectedState.OVERVIEW);
         overview.dismissByEscKey();
-        assertTrue("Launcher internal state is not Home",
-                isInState(() -> LauncherState.NORMAL));
+        assertIsInState("Launcher internal state is not Home", ExpectedState.HOME);
     }
 
     @Test
@@ -230,11 +243,11 @@
         startTestAppsWithCheck();
         startAppFast(CALCULATOR_APP_PACKAGE); // Ensure Calculator is last opened app.
         Workspace home = mLauncher.goHome();
-        assertTrue("Launcher state is not Home", isInState(() -> LauncherState.NORMAL));
+        assertIsInState("Launcher state is not Home", ExpectedState.HOME);
 
         Overview overview = home.openOverviewFromActionPlusTabKeyboardShortcut();
 
-        assertTrue("Launcher state is not Overview", isInState(() -> LauncherState.OVERVIEW));
+        assertIsInState("Launcher state is not Overview", ExpectedState.OVERVIEW);
         overview.launchFocusedTaskByEnterKey(CALCULATOR_APP_PACKAGE); // Assert app is focused.
     }
 
@@ -243,28 +256,32 @@
         startTestAppsWithCheck();
         startAppFast(CALCULATOR_APP_PACKAGE); // Ensure Calculator is last opened app.
         Workspace home = mLauncher.goHome();
-        assertTrue("Launcher state is not Home", isInState(() -> LauncherState.NORMAL));
+        assertIsInState("Launcher state is not Home", ExpectedState.HOME);
 
         Overview overview = home.openOverviewFromRecentsKeyboardShortcut();
 
-        assertTrue("Launcher state is not Overview", isInState(() -> LauncherState.OVERVIEW));
+        assertIsInState("Launcher state is not Overview", ExpectedState.OVERVIEW);
         overview.launchFocusedTaskByEnterKey(CALCULATOR_APP_PACKAGE); // Assert app is focused.
     }
 
-    private int getCurrentOverviewPage(Launcher launcher) {
-        return launcher.<RecentsView>getOverviewPanel().getCurrentPage();
+    private RecentsView getOverviewPanel(RecentsViewContainer recentsViewContainer) {
+        return recentsViewContainer.getOverviewPanel();
     }
 
-    private int getTaskCount(Launcher launcher) {
-        return launcher.<RecentsView>getOverviewPanel().getTaskViewCount();
+    private int getCurrentOverviewPage(RecentsViewContainer recentsViewContainer) {
+        return getOverviewPanel(recentsViewContainer).getCurrentPage();
     }
 
-    private int getTopRowTaskCountForTablet(Launcher launcher) {
-        return launcher.<RecentsView>getOverviewPanel().getTopRowTaskCountForTablet();
+    private int getTaskCount(RecentsViewContainer recentsViewContainer) {
+        return getOverviewPanel(recentsViewContainer).getTaskViewCount();
     }
 
-    private int getBottomRowTaskCountForTablet(Launcher launcher) {
-        return launcher.<RecentsView>getOverviewPanel().getBottomRowTaskCountForTablet();
+    private int getTopRowTaskCountForTablet(RecentsViewContainer recentsViewContainer) {
+        return getOverviewPanel(recentsViewContainer).getTopRowTaskCountForTablet();
+    }
+
+    private int getBottomRowTaskCountForTablet(RecentsViewContainer recentsViewContainer) {
+        return getOverviewPanel(recentsViewContainer).getBottomRowTaskCountForTablet();
     }
 
     @Test
@@ -274,8 +291,8 @@
         startTestAppsWithCheck();
         assertNotNull("Workspace.switchToOverview() returned null",
                 mLauncher.goHome().switchToOverview());
-        assertTrue("Launcher internal state didn't switch to Overview",
-                isInState(() -> LauncherState.OVERVIEW));
+        assertIsInState(
+                "Launcher internal state didn't switch to Overview", ExpectedState.OVERVIEW);
     }
 
     @Test
@@ -300,8 +317,8 @@
 
         assertNotNull("Background.switchToOverview() returned null",
                 launchedAppState.switchToOverview());
-        assertTrue("Launcher internal state didn't switch to Overview",
-                isInState(() -> LauncherState.OVERVIEW));
+        assertIsInState(
+                "Launcher internal state didn't switch to Overview", ExpectedState.OVERVIEW);
     }
 
     private void quickSwitchToPreviousAppAndAssert(boolean toRight) {
@@ -413,11 +430,11 @@
         // Debug if we need to goHome to prevent wrong previous state b/315525621
         mLauncher.goHome();
         mLauncher.getWorkspace().switchToAllApps().pressBackToWorkspace();
-        waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL);
+        waitForState("Launcher internal state didn't switch to Home", ExpectedState.HOME);
 
         startAppFast(CALCULATOR_APP_PACKAGE);
         mLauncher.getLaunchedAppState().pressBackToWorkspace();
-        waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL);
+        waitForState("Launcher internal state didn't switch to Home", ExpectedState.HOME);
     }
 
     @Test
@@ -432,16 +449,15 @@
         }
 
         Overview overview = mLauncher.goHome().switchToOverview();
-        executeOnLauncher(
-                launcher -> assertTrue("Don't have at least 13 tasks",
-                        getTaskCount(launcher) >= 13));
+        executeOnRecentsViewContainer(
+                container -> assertTrue("Don't have at least 13 tasks",
+                        getTaskCount(container) >= 13));
 
         // Test scroll the first task off screen
         overview.scrollCurrentTaskOffScreen();
-        assertTrue("Launcher internal state is not Overview",
-                isInState(() -> LauncherState.OVERVIEW));
-        executeOnLauncher(launcher -> assertTrue("Current task in Overview is still 0",
-                getCurrentOverviewPage(launcher) > 0));
+        assertIsInState("Launcher internal state is not Overview", ExpectedState.OVERVIEW);
+        executeOnRecentsViewContainer(container -> assertTrue("Current task in Overview is still 0",
+                getCurrentOverviewPage(container) > 0));
 
         // Test opening the task.
         overview.getCurrentTask().open();
@@ -454,41 +470,41 @@
         // Scroll the task offscreen as it is now first
         overview = mLauncher.goHome().switchToOverview();
         overview.scrollCurrentTaskOffScreen();
-        assertTrue("Launcher internal state is not Overview",
-                isInState(() -> LauncherState.OVERVIEW));
-        executeOnLauncher(launcher -> assertTrue("Current task in Overview is still 0",
-                getCurrentOverviewPage(launcher) > 0));
+        assertIsInState(
+                "Launcher internal state is not Overview", ExpectedState.OVERVIEW);
+        executeOnRecentsViewContainer(container -> assertTrue("Current task in Overview is still 0",
+                getCurrentOverviewPage(container) > 0));
 
         // Test dismissing the later task.
         final Integer numTasks = getFromLauncher(this::getTaskCount);
         overview.getCurrentTask().dismiss();
-        executeOnLauncher(
-                launcher -> assertEquals("Dismissing a task didn't remove 1 task from Overview",
-                        numTasks - 1, getTaskCount(launcher)));
-        executeOnLauncher(launcher -> assertTrue("Grid did not rebalance after dismissal",
-                (Math.abs(getTopRowTaskCountForTablet(launcher) - getBottomRowTaskCountForTablet(
-                        launcher)) <= 1)));
+        executeOnRecentsViewContainer(
+                container -> assertEquals("Dismissing a task didn't remove 1 task from Overview",
+                        numTasks - 1, getTaskCount(container)));
+        executeOnRecentsViewContainer(container -> assertTrue(
+                "Grid did not rebalance after dismissal",
+                (Math.abs(getTopRowTaskCountForTablet(container)
+                        - getBottomRowTaskCountForTablet(container)) <= 1)));
 
         // TODO(b/308841019): Re-enable after fixing Overview jank when dismiss
 //        // Test dismissing more tasks.
-//        assertTrue("Launcher internal state didn't remain in Overview",
-//                isInState(() -> LauncherState.OVERVIEW));
+//        assertIsInState(
+//                "Launcher internal state didn't remain in Overview", ExpectedState.OVERVIEW);
 //        overview.getCurrentTask().dismiss();
-//        assertTrue("Launcher internal state didn't remain in Overview",
-//                isInState(() -> LauncherState.OVERVIEW));
+//        assertIsInState(
+//                "Launcher internal state didn't remain in Overview", ExpectedState.OVERVIEW);
 //        overview.getCurrentTask().dismiss();
-//        executeOnLauncher(launcher -> assertTrue("Grid did not rebalance after multiple
-//        dismissals",
-//                (Math.abs(getTopRowTaskCountForTablet(launcher) - getBottomRowTaskCountForTablet(
-//                        launcher)) <= 1)));
+//        executeOnRecentsViewContainer(container -> assertTrue(
+//        "Grid did not rebalance after multiple dismissals",
+//                (Math.abs(getTopRowTaskCountForTablet(container)
+//                        - getBottomRowTaskCountForTablet(container)) <= 1)));
 
         // Test dismissing all tasks.
         mLauncher.goHome().switchToOverview().dismissAllTasks();
-        assertTrue("Launcher internal state is not Home",
-                isInState(() -> LauncherState.NORMAL));
-        executeOnLauncher(
-                launcher -> assertEquals("Still have tasks after dismissing all",
-                        0, getTaskCount(launcher)));
+        assertIsInState("Launcher internal state is not Home", ExpectedState.HOME);
+        executeOnRecentsViewContainer(
+                container -> assertEquals("Still have tasks after dismissing all",
+                        0, getTaskCount(container)));
     }
 
     @Test
@@ -497,22 +513,19 @@
         startTestAppsWithCheck();
 
         Overview overview = mLauncher.goHome().switchToOverview();
-        assertTrue("Launcher internal state should be Overview",
-                isInState(() -> LauncherState.OVERVIEW));
-        executeOnLauncher(
-                launcher -> assertTrue("Should have at least 3 tasks",
-                        getTaskCount(launcher) >= 3));
+        assertIsInState("Launcher internal state should be Overview", ExpectedState.OVERVIEW);
+        executeOnRecentsViewContainer(
+                container -> assertTrue("Should have at least 3 tasks",
+                        getTaskCount(container) >= 3));
 
         // It should not dismiss overview when tapping between tasks
         overview.touchBetweenTasks();
         overview = mLauncher.getOverview();
-        assertTrue("Launcher internal state should be Overview",
-                isInState(() -> LauncherState.OVERVIEW));
+        assertIsInState("Launcher internal state should be Overview", ExpectedState.OVERVIEW);
 
         // Dismiss when tapping to the right of the focused task
         overview.touchOutsideFirstTask();
-        assertTrue("Launcher internal state should be Home",
-                isInState(() -> LauncherState.NORMAL));
+        assertIsInState("Launcher internal state should be Home", ExpectedState.HOME);
     }
 
     @Test
@@ -524,34 +537,29 @@
         startTestAppsWithCheck();
 
         Overview overview = mLauncher.goHome().switchToOverview();
-        assertTrue("Launcher internal state should be Overview",
-                isInState(() -> LauncherState.OVERVIEW));
-        executeOnLauncher(
-                launcher -> assertTrue("Should have at least 3 tasks",
-                        getTaskCount(launcher) >= 3));
+        assertIsInState("Launcher internal state should be Overview", ExpectedState.OVERVIEW);
+        executeOnRecentsViewContainer(
+                container -> assertTrue("Should have at least 3 tasks",
+                        getTaskCount(container) >= 3));
 
         if (mLauncher.isTransientTaskbar()) {
             // On transient taskbar, it should dismiss when tapping outside taskbar bounds.
             overview.touchTaskbarBottomCorner(/* tapRight= */ false);
-            assertTrue("Launcher internal state should be Normal",
-                    isInState(() -> LauncherState.NORMAL));
+            assertIsInState("Launcher internal state should be Normal", ExpectedState.HOME);
 
             overview = mLauncher.getWorkspace().switchToOverview();
 
             // On transient taskbar, it should dismiss when tapping outside taskbar bounds.
             overview.touchTaskbarBottomCorner(/* tapRight= */ true);
-            assertTrue("Launcher internal state should be Normal",
-                    isInState(() -> LauncherState.NORMAL));
+            assertIsInState("Launcher internal state should be Normal", ExpectedState.HOME);
         } else {
             // On persistent taskbar, it should not dismiss when tapping the taskbar
             overview.touchTaskbarBottomCorner(/* tapRight= */ false);
-            assertTrue("Launcher internal state should be Overview",
-                    isInState(() -> LauncherState.OVERVIEW));
+            assertIsInState("Launcher internal state should be Overview", ExpectedState.OVERVIEW);
 
             // On persistent taskbar, it should not dismiss when tapping the taskbar
             overview.touchTaskbarBottomCorner(/* tapRight= */ true);
-            assertTrue("Launcher internal state should be Overview",
-                    isInState(() -> LauncherState.OVERVIEW));
+            assertIsInState("Launcher internal state should be Overview", ExpectedState.OVERVIEW);
         }
     }
 
@@ -594,4 +602,28 @@
             // Presumably the test started with 0 tasks and remains that way after going home.
         }
     }
+
+    private void assertIsInState(
+            @NonNull String failureMessage, @NonNull ExpectedState expectedState) {
+        assertTrue(failureMessage, enableLauncherOverviewInWindow()
+                ? isInRecentsWindowState(() -> expectedState.mRecentsState)
+                : isInState(() -> expectedState.mLauncherState));
+    }
+
+    private void waitForState(
+            @NonNull String failureMessage, @NonNull ExpectedState expectedState) {
+        if (enableLauncherOverviewInWindow()) {
+            waitForRecentsWindowState(failureMessage, () -> expectedState.mRecentsState);
+        } else {
+            waitForState(failureMessage, () -> expectedState.mLauncherState);
+        }
+    }
+
+    private void executeOnRecentsViewContainer(@NonNull Consumer<RecentsViewContainer> f) {
+        if (enableLauncherOverviewInWindow()) {
+            executeOnRecentsWindow(f::accept);
+        } else {
+            executeOnLauncher(f::accept);
+        }
+    }
 }
diff --git a/quickstep/tests/src/com/android/quickstep/TaskAnimationManagerTest.java b/quickstep/tests/src/com/android/quickstep/TaskAnimationManagerTest.java
index 633a575..098b417 100644
--- a/quickstep/tests/src/com/android/quickstep/TaskAnimationManagerTest.java
+++ b/quickstep/tests/src/com/android/quickstep/TaskAnimationManagerTest.java
@@ -28,8 +28,7 @@
 import android.content.Intent;
 
 import androidx.test.filters.SmallTest;
-
-import com.android.quickstep.fallback.window.RecentsWindowManager;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -40,21 +39,25 @@
 @SmallTest
 public class TaskAnimationManagerTest {
 
-    @Mock
-    private Context mContext;
-
-    @Mock
-    private RecentsWindowManager mRecentsWindowManager;
+    protected final Context mContext =
+            InstrumentationRegistry.getInstrumentation().getTargetContext();
 
     @Mock
     private SystemUiProxy mSystemUiProxy;
 
     private TaskAnimationManager mTaskAnimationManager;
+    protected RecentsAnimationDeviceState mRecentsAnimationDeviceState;
+
+    @Before
+    public void setUpRecentsAnimationDeviceState() {
+        runOnMainSync(() ->
+                mRecentsAnimationDeviceState = new RecentsAnimationDeviceState(mContext, true));
+    }
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mTaskAnimationManager = new TaskAnimationManager(mContext, mRecentsWindowManager) {
+        mTaskAnimationManager = new TaskAnimationManager(mContext, mRecentsAnimationDeviceState) {
             @Override
             SystemUiProxy getSystemUiProxy() {
                 return mSystemUiProxy;
@@ -69,8 +72,8 @@
         final RecentsAnimationCallbacks.RecentsAnimationListener listener =
                 mock(RecentsAnimationCallbacks.RecentsAnimationListener.class);
         doReturn(activityInterface).when(gestureState).getContainerInterface();
-        mTaskAnimationManager.startRecentsAnimation(gestureState, new Intent(), listener);
-
+        runOnMainSync(() ->
+                mTaskAnimationManager.startRecentsAnimation(gestureState, new Intent(), listener));
         final ArgumentCaptor<ActivityOptions> optionsCaptor =
                 ArgumentCaptor.forClass(ActivityOptions.class);
         verify(mSystemUiProxy)
@@ -78,4 +81,8 @@
         assertEquals(ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS,
                 optionsCaptor.getValue().getPendingIntentBackgroundActivityStartMode());
     }
+
+    protected static void runOnMainSync(Runnable runnable) {
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(runnable);
+    }
 }
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index d28e1f5..4dd5e1d 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Werk"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Gesprekke"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Neem notas"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"Wys Voeg By-knoppie"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"Versteek Voeg By-knoppie"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"Voeg by"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Voeg <xliff:g id="WIDGET_NAME">%1$s</xliff:g>-legstuk by"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Wys almal"</string>
@@ -101,9 +103,9 @@
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"installeer kortpaaie"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Laat \'n app toe om kortpaaie by te voeg sonder gebruikerinmenging."</string>
     <string name="permlab_read_settings" msgid="5136500343007704955">"lees tuis-instellings en -kortpaaie"</string>
-    <string name="permdesc_read_settings" msgid="4208061150510996676">"Laat die program toe om die instellings en kortpaaie op tuisskerm te lees."</string>
+    <string name="permdesc_read_settings" msgid="4208061150510996676">"Laat die app toe om die instellings en kortpaaie op tuisskerm te lees."</string>
     <string name="permlab_write_settings" msgid="4820028712156303762">"skryf tuis-instellings en -kortpaaie"</string>
-    <string name="permdesc_write_settings" msgid="726859348127868466">"Laat die program toe om die instellings en kortpaaie op tuisskerm te verander."</string>
+    <string name="permdesc_write_settings" msgid="726859348127868466">"Laat die app toe om die instellings en kortpaaie op tuisskerm te verander."</string>
     <string name="gadget_error_text" msgid="740356548025791839">"Kan nie legstuk laai nie"</string>
     <string name="gadget_setup_text" msgid="8348374825537681407">"Legstukinstellings"</string>
     <string name="gadget_complete_setup_text" msgid="309040266978007925">"Tik om opstelling te voltooi"</string>
@@ -151,7 +153,7 @@
     <string name="app_archived_title" msgid="4548283110222420708">"<xliff:g id="NAME">%1$s</xliff:g> is geargiveer."</string>
     <string name="app_unarchiving_action" msgid="5736107006413929484">"laai af en stel terug"</string>
     <string name="dialog_update_title" msgid="114234265740994042">"Programopdatering word vereis"</string>
-    <string name="dialog_update_message" msgid="4176784553982226114">"Die program vir hierdie ikoon is nie opgedateer nie. Jy kan dit handmatig opdateer om hierdie kortpad weer te aktiveer, of die ikoon verwyder."</string>
+    <string name="dialog_update_message" msgid="4176784553982226114">"Die app vir hierdie ikoon is nie opgedateer nie. Jy kan dit handmatig opdateer om hierdie kortpad weer te aktiveer, of die ikoon verwyder."</string>
     <string name="dialog_update" msgid="2178028071796141234">"Dateer op"</string>
     <string name="dialog_remove" msgid="6510806469849709407">"Verwyder"</string>
     <string name="widgets_list" msgid="796804551140113767">"Legstukkelys"</string>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index 6ed533e..c40ef69 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"ሥራ"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"ውይይቶች"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"የማስታወሻ አያያዝ"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"የማከል አዝራርን አሳይ"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"የማከል አዝራርን ደብቅ"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"አክል"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"ምግብር <xliff:g id="WIDGET_NAME">%1$s</xliff:g>ን አክል"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"ሁሉንም አሳይ"</string>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index e924f4d..bbdfebd 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"تطبيقات العمل"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"المحادثات"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"تدوين الملاحظات"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"إضافة"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"إضافة التطبيق المصغّر \"<xliff:g id="WIDGET_NAME">%1$s</xliff:g>\""</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"عرض الكل"</string>
diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml
index 1e88444..acce0d2 100644
--- a/res/values-as/strings.xml
+++ b/res/values-as/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"কৰ্মস্থান"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"বাৰ্তালাপ"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"টোকা গ্ৰহণ কৰা"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"যোগ দিয়ক বুটামটো দেখুৱাওক"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"যোগ দিয়ক বুটামটো লুকুৱাওক"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"যোগ দিয়ক"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> ৱিজেট যোগ দিয়ক"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"আটাইবোৰ দেখুৱাওক"</string>
diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml
index 516b401..8eda426 100644
--- a/res/values-az/strings.xml
+++ b/res/values-az/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"İş"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Söhbətlər"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Qeydgötürmə"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"Əlavə edin düyməsini göstərin"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"Əlavə edin düyməsini gizlədin"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"Əlavə edin"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> vidcet əlavə edin"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Hamısını göstər"</string>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index c8aaa88..33941ff 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Posao"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Konverzacije"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Pravljenje beležaka"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"Prikažite dugme za dodavanje"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"Sakrijte dugme za dodavanje"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"Dodaj"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Dodajte vidžet <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Prikaži sve"</string>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index b2f2f32..d395006 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Працоўныя"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Размовы"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Стварэнне нататак"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"Паказаць кнопку \"Дадаць\""</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"Схаваць кнопку \"Дадаць\""</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"Дадаць"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Дадаць віджэт \"<xliff:g id="WIDGET_NAME">%1$s</xliff:g>\""</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Паказаць усе"</string>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index f4fb396..cf298c3 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Служебни"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Разговори"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Водене на бележки"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"Показване на бутона за добавяне"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"Скриване на бутона за добавяне"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"Добавяне"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Добавяне на приспособлението „<xliff:g id="WIDGET_NAME">%1$s</xliff:g>“"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Вижте всички"</string>
diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml
index 1d38e66..f2654bb 100644
--- a/res/values-bn/strings.xml
+++ b/res/values-bn/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"অফিস"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"কথোপকথন"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"নোট নেওয়া"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"যোগ করার বোতাম দেখুন"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"যোগ করার বোতাম লুকান"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"যোগ করুন"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> উইজেট যোগ করুন"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"সব দেখুন"</string>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index 57b475c..3277e22 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Posao"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Razgovori"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Pisanje bilješki"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"Prikazivanje dugmeta za dodavanje"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"Sakrivanje dugmeta za dodavanje"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"Dodajte"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Dodavanje vidžeta <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Prikaži sve"</string>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index 5c7409f..f8f8e28 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Treball"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Converses"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Presa de notes"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"Mostra el botó Afegeix"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"Amaga el botó Afegeix"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"Afegeix"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Afegeix el widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Mostra-ho tot"</string>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index e5f26da..5fa9154 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Práce"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Konverzace"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Psaní poznámek"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"Zobrazit tlačítko přidání"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"Skrýt tlačítko přidání"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"Přidat"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Přidat widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Zobrazit vše"</string>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 1fb015f..16e3473 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Arbejde"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Samtaler"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Notetagning"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"Vis knappen Tilføj"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"Skjul knappen Tilføj"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"Tilføj"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Tilføj <xliff:g id="WIDGET_NAME">%1$s</xliff:g>-widget"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Vis alle"</string>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 6eb50a7..cb64821 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Geschäftlich"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Unterhaltungen"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Notizen"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"Schaltfläche „Hinzufügen“ anzeigen"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"Schaltfläche „Hinzufügen“ ausblenden"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"Hinzufügen"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Widget „<xliff:g id="WIDGET_NAME">%1$s</xliff:g>“ hinzufügen"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Alle anzeigen"</string>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index deaf4ff..672ba05 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Εργασίας"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Συζητήσεις"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Δημιουργία σημειώσεων"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"Εμφάνιση κουμπιού προσθήκης"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"Απόκρυψη κουμπιού προσθήκης"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"Προσθήκη"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Προσθήκη του γραφικού στοιχείου <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Εμφάνιση όλων"</string>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index 1175601..a4f88de 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Work"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Conversations"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Note-taking"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"Show add button"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"Hide add button"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"Add"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Add <xliff:g id="WIDGET_NAME">%1$s</xliff:g> widget"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Show all"</string>
diff --git a/res/values-en-rCA/strings.xml b/res/values-en-rCA/strings.xml
index 1dc6d16..363bb4b 100644
--- a/res/values-en-rCA/strings.xml
+++ b/res/values-en-rCA/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Work"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Conversations"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Note-taking"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"Show add button"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"Hide add button"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"Add"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Add <xliff:g id="WIDGET_NAME">%1$s</xliff:g> widget"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Show all"</string>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index 1175601..a4f88de 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Work"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Conversations"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Note-taking"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"Show add button"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"Hide add button"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"Add"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Add <xliff:g id="WIDGET_NAME">%1$s</xliff:g> widget"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Show all"</string>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index 1175601..a4f88de 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Work"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Conversations"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Note-taking"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"Show add button"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"Hide add button"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"Add"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Add <xliff:g id="WIDGET_NAME">%1$s</xliff:g> widget"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Show all"</string>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index 7006bfb..0344113 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Trabajo"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Conversaciones"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Tomar notas"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"Mostrar botón Agregar"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"Ocultar botón Agregar"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"Agregar"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Agregar widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Mostrar todos"</string>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index dabba16..fc34acf 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Trabajo"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Conversaciones"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Toma de notas"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"Mostrar el botón Añadir"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"Ocultar el botón Añadir"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"Añadir"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Añadir widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Mostrar todo"</string>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index 72f6bb8..5fc3b82 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Töö"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Vestlused"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Märkmete tegemine"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"Kuva lisamisnupp"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"Peida lisamisnupp"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"Lisa"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Lisa vidin <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Kuva kõik"</string>
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
index 37d3e4f..99f127a 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Lanekoak"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Elkarrizketak"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Oharrak idazteko"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"Erakutsi gehitzeko botoia"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"Ezkutatu gehitzeko botoia"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"Gehitu"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Gehitu <xliff:g id="WIDGET_NAME">%1$s</xliff:g> widgeta"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Erakutsi guztiak"</string>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index 11130e4..99e8718 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"ابزاره‌های کاری"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"مکالمه‌ها"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"یادداشت‌برداری"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"نشان دادن دکمه افزودن"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"پنهان کردن دکمه افزودن"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"افزودن"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"افزودن ابزاره <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"نمایش همه"</string>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index 3a25c51..6beb593 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Työ"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Keskustelut"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Muistiinpanojen tekeminen"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"Näytä lisää-painike"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"Piilota Lisää-painike"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"Lisää"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Lisää widget: <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Näytä kaikki"</string>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index 45a0403..696d8af 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Professionnels"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Conversations"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Prise de note"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"Afficher le bouton Ajouter"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"Masquer le bouton Ajouter"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"Ajouter"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Ajoutez le widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Tout afficher"</string>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 0a61189..c870afa 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Professionnels"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Conversations"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Prise de notes"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"Afficher le bouton \"Ajouter\""</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"Masquer le bouton \"Ajouter\""</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"Ajouter"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Ajoutez un widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Tout afficher"</string>
diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml
index 293a6b3..5bcf2fd 100644
--- a/res/values-gl/strings.xml
+++ b/res/values-gl/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Widgets do traballo"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Conversas"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Toma de notas"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"Mostrar o botón de engadir"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"Ocultar o botón de engadir"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"Engadir"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Engadir o widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Mostrar todo"</string>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index c7d3a10..1480ea9 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"ઑફિસ"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"વાતચીતો"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"નોંધ લેવી"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"\'ઉમેરો\' બટન બતાવો"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"\'ઉમેરો\' બટન છુપાવો"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"ઉમેરો"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> વિજેટ ઉમેરો"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"બધા બતાવો"</string>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index 1bf63d6..32a2a96 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"वर्क विजेट"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"बातचीत"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"नोट बनाने से जुड़े विजेट"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"\'जोड़ें\' बटन दिखाएं"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"\'जोड़ें\' बटन छिपाएं"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"जोड़ें"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> विजेट जोड़ें"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"सभी दिखाएं"</string>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index 7bf6f33..726efe8 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Posao"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Razgovori"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Pisanje bilježaka"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"Prikaži gumb za dodavanje"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"Sakrij gumb za dodavanje"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"Dodaj"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Dodaj widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Prikaži sve"</string>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index e82566e..b85d3df 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Munka"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Beszélgetések"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Jegyzetelés"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"Hozzáadás gomb megjelenítése"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"Hozzáadás gomb elrejtése"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"Hozzáadás"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> modul hozzáadása"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Az összes megjelenítése"</string>
diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml
index 4879d4d..36e89b4 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Աշխատանքային"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Զրույցներ"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Նշումների ստեղծում"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"Ցույց տալ «Ավելացնել» կոճակը"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"Թաքցնել «Ավելացնել» կոճակը"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"Ավելացնել"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Ավելացնել <xliff:g id="WIDGET_NAME">%1$s</xliff:g> վիջեթը"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Բոլորը"</string>
@@ -137,7 +139,7 @@
     <string name="msg_missing_notification_access" msgid="281113995110910548">"Ծանուցումների կետիկները ցուցադրելու համար միացրեք ծանուցումները <xliff:g id="NAME">%1$s</xliff:g>-ի համար"</string>
     <string name="title_change_settings" msgid="1376365968844349552">"Փոխել կարգավորումները"</string>
     <string name="notification_dots_service_title" msgid="4284221181793592871">"Ցուցադրել ծանուցումների կետիկները"</string>
-    <string name="developer_options_title" msgid="700788437593726194">"Մշակողի ընտրանքներ"</string>
+    <string name="developer_options_title" msgid="700788437593726194">"Ծրագրավորողի ընտրանքներ"</string>
     <string name="auto_add_shortcuts_label" msgid="4926805029653694105">"Ավելացնել պատկերակները հիմնական էկրանին"</string>
     <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Նոր հավելվածների համար"</string>
     <string name="package_state_unknown" msgid="7592128424511031410">"Անհայտ է"</string>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index dde261a..0d5a819 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Kerja"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Percakapan"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Pembuatan catatan"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"Tampilkan tombol tambahkan"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"Sembunyikan tombol tambahkan"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"Tambahkan"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Tambahkan widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Tampilkan semua"</string>
diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml
index 0698bab..8f3c604 100644
--- a/res/values-is/strings.xml
+++ b/res/values-is/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Vinna"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Samtöl"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Glósugerð"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"Sýna hnapp til að bæta við"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"Fela hnapp til að bæta við"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"Bæta við"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Bæta græjunni <xliff:g id="WIDGET_NAME">%1$s</xliff:g> við"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Sýna allt"</string>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index fdd62a4..f125816 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Lavoro"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Conversazioni"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Aggiunta di note"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"Mostra pulsante Aggiungi"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"Nascondi pulsante Aggiungi"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"Aggiungi"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Aggiungi widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Mostra tutto"</string>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index cfc0291..f7fd83a 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"ווידג\'טים לעבודה"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"שיחות"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"כתיבת הערות"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"הצגת לחצן ההוספה"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"הסתרת לחצן ההוספה"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"הוספה"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"הוספת הווידג\'ט <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"הצגת הכול"</string>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index 01626ab..9b012c5 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"仕事用"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"会話"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"メモ"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"追加ボタンを表示する"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"追加ボタンを非表示にする"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"追加"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g>ウィジェットを追加"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"すべて表示"</string>
diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml
index 536c1ad..955d65f 100644
--- a/res/values-ka/strings.xml
+++ b/res/values-ka/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"სამსახური"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"მიმოწერები"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"ჩანიშვნა"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"დამატების ღილაკის ჩვენება"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"დამატების ღილაკის დამალვა"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"დამატება"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> ვიჯეტის დამატება"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"ყველას ჩვენება"</string>
diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml
index b7f38b9..c50d007 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Жұмыс виджеттері"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Әңгімелер"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Ескертпе жазу"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"Қосу түймесін көрсету"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"Қосу түймесін жасыру"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"Қосу"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Виджет (<xliff:g id="WIDGET_NAME">%1$s</xliff:g>) қосу"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Барлығын көру"</string>
diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml
index 6a707ae..7a73e69 100644
--- a/res/values-km/strings.xml
+++ b/res/values-km/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"ការងារ"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"ការសន្ទនា"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"ការកត់ត្រា"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"បង្ហាញប៊ូតុង \"បញ្ចូល\""</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"លាក់ប៊ូតុង \"បញ្ចូល\""</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"បញ្ចូល"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"បញ្ចូលធាតុ​ក្រាហ្វិក <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"បង្ហាញ​ទាំងអស់"</string>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index 0ce1a0f..8c988bf 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"ಕೆಲಸ"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"ಸಂಭಾಷಣೆಗಳು"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"ಟಿಪ್ಪಣಿ ತೆಗೆದುಕೊಳ್ಳುವುದು"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"ಸೇರಿಸಿ ಬಟನ್ ಅನ್ನು ತೋರಿಸಿ"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"ಸೇರಿಸಿ ಬಟನ್ ಅನ್ನು ಮರೆಮಾಡಿ"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"ಸೇರಿಸಿ"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> ವಿಜೆಟ್ ಸೇರಿಸಿ"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"ಎಲ್ಲಾ ತೋರಿಸಿ"</string>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index 19e75d1..9f7217b 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"직장 위젯"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"대화"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"메모"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"추가 버튼 표시"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"추가 버튼 숨기기"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"추가"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> 위젯 추가"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"모두 표시"</string>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index 901b2bd..b7ce9d7 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Жумуш"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Сүйлөшүүлөр"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Эскертме жазуу"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"Кошуу баскычын көрсөтүү"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"Кошуу баскычын жашыруу"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"Кошуу"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> виджетин кошуу"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Баарын көрсөтүү"</string>
diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml
index f01dc67..33fff3d 100644
--- a/res/values-lo/strings.xml
+++ b/res/values-lo/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"ວຽກ"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"ການສົນທະນາ"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"ການຈົດບັນທຶກ"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"ສະແດງປຸ່ມເພີ່ມ"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"ເຊື່ອງປຸ່ມເພີ່ມ"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"ເພີ່ມ"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"ເພີ່ມວິດເຈັດ <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"ສະແດງທັງໝົດ"</string>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index f5e21de..0d0f5db 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Darbas"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Pokalbiai"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Užrašų kūrimas"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"Rodyti mygtuką „Pridėti“"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"Slėpti mygtuką „Pridėti“"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"Pridėti"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Pridėti valdiklį: <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Rodyti viską"</string>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index 3eafd9a..c86c6bb 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Darba"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Sarunas"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Piezīmju pierakstīšana"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"Rādīt pogu Pievienot"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"Paslēpt pogu Pievienot"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"Pievienot"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Pievienot logrīku <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Rādīt visus"</string>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
index 2b85e12..2685c60 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Работни"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Разговори"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Фаќање белешки"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"Додај"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Додај го виџетот <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Прикажи ги сите"</string>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
index df98b72..2c82585 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"ജോലി"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"സംഭാഷണങ്ങൾ"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"കുറിപ്പ് രേഖപ്പെടുത്തൽ"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"\'ചേർക്കുക ബട്ടൺ\' കാണിക്കുക"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"\'ചേർക്കുക ബട്ടൺ\' മറയ്ക്കുക"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"ചേർക്കുക"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> വിജറ്റ് ചേർക്കുക"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"എല്ലാം കാണിക്കൂ"</string>
diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml
index 9fdaf13..434a731 100644
--- a/res/values-mn/strings.xml
+++ b/res/values-mn/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Ажил"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Харилцан яриа"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Тэмдэглэл хөтлөх"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"Нэмэх товчийг харуулах"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"Нэмэх товчийг нуух"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"Нэмэх"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> виджетийг нэмэх"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Бүгдийг харуул"</string>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index e596193..c872cc6 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"ऑफिस"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"संभाषणे"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"टिपा घेणे"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"जोडा बटण दाखवा"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"जोडा बटण लपवा"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"जोडा"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> विजेट जोडा"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"सर्व दाखवा"</string>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index 91c85de..f5dca93 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Kerja"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Perbualan"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Pengambilan nota"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"Tunjukkan butang tambah"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"Sembunyikan butang tambah"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"Tambah"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Tambahkan widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Tunjukkan semua"</string>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index 8ed7980..24f4435 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"အလုပ်"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"စကားဝိုင်းများ"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"မှတ်စုလိုက်ခြင်း"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"ထည့်ရန်ခလုတ် ပြပါ"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"ထည့်ရန်ခလုတ် ဖျောက်ပါ"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"ထည့်ရန်"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> ဝိဂျက်ထည့်ရန်"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"အားလုံးပြပါ"</string>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index fa7a047..a79740d 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Jobb"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Samtaler"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Notatskriving"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"Vis Legg til-knappen"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"Skjul Legg til-knappen"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"Legg til"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Legg til <xliff:g id="WIDGET_NAME">%1$s</xliff:g>-modulen"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Vis alle"</string>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index 86ebc7b..f35b943 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"कामसम्बन्धी"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"वार्तालापहरू"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"नोट लेख्ने कार्य"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"\"हाल्नुहोस्\" नामक बटन देखाउनुहोस्"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"\"हाल्नुहोस्\" नामक बटन लुकाउनुहोस्"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"हाल्नुहोस्"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> विजेट हाल्नुहोस्"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"सबै देखाउनुहोस्"</string>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index 1e3e166..5083976 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Werk"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Gesprekken"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Aantekeningen maken"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"Knop Toevoegen tonen"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"Knop Toevoegen verbergen"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"Toevoegen"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g> toevoegen"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Alles tonen"</string>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
index f8d68ae..a26a7c5 100644
--- a/res/values-or/strings.xml
+++ b/res/values-or/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"ୱାର୍କ"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"ବାର୍ତ୍ତାଳାପଗୁଡ଼ିକ"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"ନୋଟ-ଟେକିଂ"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"\'ଯୋଗ କରନ୍ତୁ\' ବଟନକୁ ଦେଖାନ୍ତୁ"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"\'ଯୋଗ କରନ୍ତୁ\' ବଟନକୁ ଲୁଚାନ୍ତୁ"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"ଯୋଗ କରନ୍ତୁ"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> ୱିଜେଟ ଯୋଗ କରନ୍ତୁ"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"ସବୁ ଦେଖାନ୍ତୁ"</string>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index 07aae76..feda5b5 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"ਕੰਮ"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"ਗੱਲਾਂਬਾਤਾਂ"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"ਨੋਟ ਬਣਾਉਣਾ"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"\'ਸ਼ਾਮਲ ਕਰੋ\' ਬਟਨ ਦਿਖਾਓ"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"\'ਸ਼ਾਮਲ ਕਰੋ\' ਬਟਨ ਲੁਕਾਓ"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"ਸ਼ਾਮਲ ਕਰੋ"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> ਵਿਜੇਟ ਸ਼ਾਮਲ ਕਰੋ"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"ਸਭ ਦਿਖਾਓ"</string>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index f76e37b..bf1299d 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Służbowe"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Rozmowy"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Notatki"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"Pokaż przycisk Dodaj"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"Ukryj przycisk Dodaj"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"Dodaj"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Dodaj widżet <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Pokaż wszystko"</string>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 66dc18f..17af437 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Trabalho"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Conversas"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Tomar notas"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"Mostrar botão para adicionar"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"Ocultar botão para adicionar"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"Adicionar"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Adicione o widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Mostrar tudo"</string>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index b9ead80..4bc8e7c 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Trabalho"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Conversas"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Anotações"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"Mostrar botão de adição"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"Ocultar botão de adição"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"Adicionar"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Adicionar o widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Mostrar tudo"</string>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index 73a5ffb..5763edb 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Serviciu"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Conversații"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Luare de notițe"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"Afișează butonul de adăugare"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"Ascunde butonul de adăugare"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"Adaugă"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Adaugă widgetul <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Afișează tot"</string>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 54a1024..d9f0b40 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Рабочие виджеты"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Разговоры"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Создание заметок"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"Показать кнопку добавления виджета"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"Скрыть кнопку добавления виджета"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"Добавить"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Добавить виджет \"<xliff:g id="WIDGET_NAME">%1$s</xliff:g>\""</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Показать все"</string>
diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml
index 21ac9ac..756d6fe 100644
--- a/res/values-si/strings.xml
+++ b/res/values-si/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"කාර්යාලය"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"සංවාද"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"සටහන් කර ගැනීම"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"එක් කිරීමේ බොත්තම පෙන්වන්න"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"එක් කිරීමේ බොත්තම සඟවන්න"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"එක් කරන්න"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> විජට්ටුව එක් කරන්න"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"සියල්ල පෙන්වන්න"</string>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index bf96af6..5fc0c41 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Pracovné"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Konverzácie"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Zapisovanie poznámok"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"Zobraziť tlačidlo Pridať"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"Skryť tlačidlo Pridať"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"Pridať"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Pridať miniaplikáciu <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Zobraziť všetko"</string>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index b7ecd50..5ab0106 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Služba"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Pogovori"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Ustvarjanje zapiskov"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"Pokaži gumb za dodajanje"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"Skrij gumb za dodajanje"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"Dodaj"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Dodajanje pripomočka »<xliff:g id="WIDGET_NAME">%1$s</xliff:g>«"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Pokaži vse"</string>
diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml
index 64bb03f..995db0f 100644
--- a/res/values-sq/strings.xml
+++ b/res/values-sq/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Puna"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Bisedat"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Mbajtja e shënimeve"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"Shto"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Shto miniaplikacionin <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Shfaq të gjitha"</string>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 44bc9b5..2b2fac8 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Посао"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Конверзације"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Прављење бележака"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"Прикажите дугме за додавање"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"Сакријте дугме за додавање"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"Додај"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Додајте виџет <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Прикажи све"</string>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index 217e116..74e35ac 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Arbete"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Konversationer"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Anteckna"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"Visa knappen Lägg till"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"Dölj knappen Lägg till"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"Lägg till"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Lägg till widgeten <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Visa alla"</string>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index 4a2db28..d696410 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Kazini"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Mazungumzo"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Kuandika madokezo"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"Onyesha kitufe cha kuweka"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"Ficha kitufe cha kuweka"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"Weka"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Weka wijeti ya <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Onyesha zote"</string>
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index e724f36..0b94252 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"பணி"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"உரையாடல்கள்"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"குறிப்பெடுத்தல்"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"சேர்ப்பதற்கான பட்டனைக் காட்டும்"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"சேர்ப்பதற்கான பட்டனை மறைக்கும்"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"சேர்"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> விட்ஜெட்டைச் சேர்க்கும்"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"எல்லாம் காட்டு"</string>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index 186e94d..90ebe1e 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"ఆఫీస్"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"సంభాషణలు"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"నోట్-టేకింగ్"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"జోడించే బటన్‌ను చూపండి"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"జోడించే బటన్‌ను దాచండి"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"జోడించండి"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> విడ్జెట్‌ను జోడించండి"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"అన్నీ చూడండి"</string>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index b5373f6..08944ed 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"งาน"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"การสนทนา"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"การจดบันทึก"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"แสดงปุ่มเพิ่ม"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"ซ่อนปุ่มเพิ่ม"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"เพิ่ม"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"เพิ่มวิดเจ็ต <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"แสดงทั้งหมด"</string>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index 7bc7f9d..b504adc 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Trabaho"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Mga Pag-uusap"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Pagtatala"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"Ipakita ang button na magdagdag"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"I-hide ang button na magdagdag"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"Idagdag"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Idagdag ang widget na <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Ipakita lahat"</string>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index 9990967..094b597 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"İş"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Görüşmeler"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Not alma"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"Ekle düğmesini göster"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"Ekle düğmesini gizle"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"Ekle"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> widget\'ı ekle"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Tümünü göster"</string>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index 165ab49..f90998d 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Робочі"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Розмови"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Створення нотаток"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"Показати кнопку \"Додати\""</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"Сховати кнопку \"Додати\""</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"Додати"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Додати віджет \"<xliff:g id="WIDGET_NAME">%1$s</xliff:g>\""</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Показати всі"</string>
diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml
index 1bd2e4a..1a90602 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"دفتری ویجیٹس"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"گفتگوئیں"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"نوٹ لکھنا"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"شامل کریں بٹن دکھائیں"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"شامل کریں بٹن چھپائیں"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"شامل کریں"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> ویجیٹ شامل کریں"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"سبھی دکھائیں"</string>
diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml
index b9286ec..62fede8 100644
--- a/res/values-uz/strings.xml
+++ b/res/values-uz/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Ish"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Suhbatlar"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Qayd olish"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"Qoʻshish tugmasini koʻrsatish"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"Qoʻshish tugmasini berkitish"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"Chiqarish"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> vidjetini chiqarish"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Hammasi"</string>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index 6082ef6..8e5f75c 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Công việc"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Cuộc trò chuyện"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Ghi chú"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"Hiện nút thêm"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"Ẩn nút thêm"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"Thêm"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Thêm tiện ích <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Hiện tất cả"</string>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index c1917a9..edc5646 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"工作"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"对话"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"记事"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"显示“添加”按钮"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"隐藏“添加”按钮"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"添加"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"添加“<xliff:g id="WIDGET_NAME">%1$s</xliff:g>”微件"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"全部显示"</string>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index d29653c..7f4ac59 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"工作"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"對話"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"做筆記"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"顯示新增按鈕"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"隱藏新增按鈕"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"新增"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"加<xliff:g id="WIDGET_NAME">%1$s</xliff:g>小工具"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"顯示全部"</string>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index eaa5a73..f527a2a 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"工作"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"對話"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"做筆記"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"顯示新增按鈕"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"隱藏新增按鈕"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"新增"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"新增「<xliff:g id="WIDGET_NAME">%1$s</xliff:g>」小工具"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"全部顯示"</string>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index 60dffeb..3c3a75c 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Umsebenzi"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Izingxoxo"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Ukuthatha amanothi"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"Bonisa inkinobho yokwengeza"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"Fihla inkinobho yokwengeza"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"Engeza"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Engeza iwijethi ye-<xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Bonisa konke"</string>
diff --git a/res/values/config.xml b/res/values/config.xml
index f6f3c95..a545f0c 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -68,7 +68,6 @@
     <string name="app_launch_tracker_class" translatable="false"></string>
     <string name="test_information_handler_class" translatable="false"></string>
     <string name="model_delegate_class" translatable="false"></string>
-    <string name="window_manager_proxy_class" translatable="false"></string>
     <string name="secondary_display_predictions_class" translatable="false"></string>
     <string name="widget_holder_factory_class" translatable="false"></string>
     <string name="taskbar_search_session_controller_class" translatable="false"></string>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index c0bd956..21aabfa 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -300,10 +300,6 @@
     <!-- the distance an icon must be dragged before button drop targets accept it -->
     <dimen name="drag_distanceThreshold">30dp</dimen>
 
-    <!-- Elevation for the drag view. It should be larger than elevation of all other drag sources
-         and drop targets like all-apps and folders -->
-    <dimen name="drag_elevation">30dp</dimen>
-
     <dimen name="drag_flingToDeleteMinVelocity">-1500dp</dimen>
 
     <dimen name="spring_loaded_panel_border">2dp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index cdfbefe..6f293b6 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -190,6 +190,8 @@
     <!-- Label for the header text of the All Apps section in All Apps view, used to separate Predicted Apps and Actions section from All Apps section. [CHAR_LIMIT=50] -->
     <string name="all_apps_label">All apps</string>
 
+    <string name="all_apps_list_label">Apps list</string>
+
     <!-- Popup items -->
     <!-- Text to display as the header above notifications. [CHAR_LIMIT=30] -->
     <string name="notifications_header">Notifications</string>
@@ -212,6 +214,7 @@
     <string name="all_apps_button_personal_label">Personal apps list</string>
     <string name="all_apps_button_work_label">Work apps list</string>
 
+    <!-- System shortcuts -->
     <!-- Label for remove drop target (from the homescreen only).
          May appear next to uninstall_drop_target_label [CHAR_LIMIT=20] -->
     <string name="remove_drop_target_label">Remove</string>
@@ -232,6 +235,8 @@
     <string name="pin_prediction">Pin Prediction</string>
     <!-- Label for bubbling a launcher item. [CHAR_LIMIT=20] -->
     <string name="bubble">Bubble</string>
+    <!-- Label for pinning an item to the taskbar. [CHAR_LIMIT=20] -->
+    <string name="pin_to_taskbar">Pin to taskbar</string>
 
     <!-- Permissions: -->
     <skip />
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 247ee48..c78666e 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -743,7 +743,7 @@
         appTitleBounds = new RectF((tmpRect.width() - titleLength) / 2.f - getCompoundPaddingLeft(),
                 0, (tmpRect.width() + titleLength) / 2.f + getCompoundPaddingRight(),
                 (int) Math.ceil(fm.bottom - fm.top));
-        appTitleBounds.inset(mRoundRectPadding * 2, 0);
+        appTitleBounds.inset((mAppTitleHorizontalPadding) * 2, 0);
 
 
         if (mIcon != null) {
@@ -1351,12 +1351,10 @@
             mIconLoadRequest.cancel();
             mIconLoadRequest = null;
         }
-        if (getTag() instanceof ItemInfoWithIcon && !mHighResUpdateInProgress) {
-            ItemInfoWithIcon info = (ItemInfoWithIcon) getTag();
-            if (info.usingLowResIcon()) {
-                mIconLoadRequest = LauncherAppState.getInstance(getContext()).getIconCache()
-                        .updateIconInBackground(BubbleTextView.this, info);
-            }
+        if (getTag() instanceof ItemInfoWithIcon info && !mHighResUpdateInProgress
+                && info.getMatchingLookupFlag().useLowRes()) {
+            mIconLoadRequest = LauncherAppState.getInstance(getContext()).getIconCache()
+                    .updateIconInBackground(BubbleTextView.this, info);
         }
     }
 
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index df5f520..3d71ff1 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -1816,7 +1816,8 @@
                 + (int) Math.ceil(getUnusedHorizontalSpace() / 2f);
         final int vStartPadding = getPaddingTop();
 
-        int x = hStartPadding + (cellX * mBorderSpace.x) + (cellX * cellWidth);
+        int x = hStartPadding + (cellX * mBorderSpace.x) + (cellX * cellWidth)
+                + getTranslationXForCell(cellX, cellY);
         int y = vStartPadding + (cellY * mBorderSpace.y) + (cellY * cellHeight);
 
         int width = cellHSpan * cellWidth + ((cellHSpan - 1) * mBorderSpace.x);
@@ -1825,6 +1826,11 @@
         resultRect.set(x, y, x + width, y + height);
     }
 
+    /** Enables successors to provide an X adjustment for the cell. */
+    protected int getTranslationXForCell(int cellX, int cellY) {
+        return 0;
+    }
+
     public void markCellsAsOccupiedForView(View view) {
         if (view instanceof LauncherAppWidgetHostView
                 && view.getTag() instanceof LauncherAppWidgetInfo) {
diff --git a/src/com/android/launcher3/CheckLongPressHelper.java b/src/com/android/launcher3/CheckLongPressHelper.java
index 3e4e96b..e9cd16c 100644
--- a/src/com/android/launcher3/CheckLongPressHelper.java
+++ b/src/com/android/launcher3/CheckLongPressHelper.java
@@ -151,6 +151,8 @@
                 handled = mView.performLongClick();
             }
             if (handled) {
+                // Cancel any default long-press action on the view
+                mView.cancelLongPress();
                 mView.setPressed(false);
                 mHasPerformedLongPress = true;
             }
diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java
index 425f277..89d54f8 100644
--- a/src/com/android/launcher3/DeleteDropTarget.java
+++ b/src/com/android/launcher3/DeleteDropTarget.java
@@ -22,6 +22,7 @@
 import android.content.Context;
 import android.text.TextUtils;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.View;
 
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
@@ -131,6 +132,10 @@
         ItemInfo item = d.dragInfo;
         if (canRemove(item)) {
             mDropTargetHandler.onDeleteComplete(item);
+        } else if (mText == getResources().getText(R.string.remove_drop_target_label)) {
+            Log.wtf("b/379606516", "If the drop target text is 'remove', then"
+                    + " users should always be able to delete the item from launcher's db."
+                    + " Invalid drag ItemInfo: " + item);
         }
     }
 
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index f1274dc..5387815 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -33,7 +33,6 @@
 import static com.android.launcher3.testing.shared.ResourceUtils.pxFromDp;
 import static com.android.launcher3.testing.shared.ResourceUtils.roundPxValueFromFloat;
 import static com.android.wm.shell.Flags.enableBubbleBar;
-import static com.android.wm.shell.Flags.enableBubbleBarInPersistentTaskBar;
 import static com.android.wm.shell.Flags.enableTinyTaskbar;
 
 import android.annotation.SuppressLint;
@@ -2433,7 +2432,6 @@
      */
     public boolean shouldAdjustHotseatOnNavBarLocationUpdate(Context context) {
         return enableBubbleBar()
-                && enableBubbleBarInPersistentTaskBar()
                 && !DisplayController.getNavigationMode(context).hasGestures;
     }
 
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index b20d8a5..1c18179 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -36,6 +36,7 @@
 import androidx.annotation.IntDef;
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.ShortcutAndWidgetContainer.TranslationProvider;
 import com.android.launcher3.celllayout.CellLayoutLayoutParams;
 import com.android.launcher3.util.HorizontalInsettableView;
 import com.android.launcher3.util.MultiPropertyFactory;
@@ -233,6 +234,13 @@
     }
 
     @Override
+    protected int getTranslationXForCell(int cellX, int cellY) {
+        TranslationProvider translationProvider = getShortcutsAndWidgets().getTranslationProvider();
+        if (translationProvider == null) return 0;
+        return (int) translationProvider.getTranslationX(cellX);
+    }
+
+    @Override
     public void setInsets(Rect insets) {
         FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
         DeviceProfile grid = mActivity.getDeviceProfile();
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 5becdde..28293d1 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3;
 
+import static com.android.launcher3.LauncherPrefs.DB_FILE;
 import static com.android.launcher3.LauncherPrefs.FIXED_LANDSCAPE_MODE;
 import static com.android.launcher3.LauncherPrefs.GRID_NAME;
 import static com.android.launcher3.Utilities.dpiFromPx;
@@ -53,8 +54,10 @@
 
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.icons.DotRenderer;
+import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.model.DeviceGridState;
 import com.android.launcher3.provider.RestoreDbTask;
+import com.android.launcher3.settings.SettingsActivity;
 import com.android.launcher3.testing.shared.ResourceUtils;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.DisplayController.Info;
@@ -86,6 +89,8 @@
     public static final MainThreadInitializedObject<InvariantDeviceProfile> INSTANCE =
             new MainThreadInitializedObject<>(InvariantDeviceProfile::new);
 
+    public static final String GRID_NAME_PREFS_KEY = "idp_grid_name";
+
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({TYPE_PHONE, TYPE_MULTI_DISPLAY, TYPE_TABLET})
     public @interface DeviceType {
@@ -242,7 +247,6 @@
     private InvariantDeviceProfile(Context context) {
         String gridName = getCurrentGridName(context);
         initGrid(context, gridName);
-
         DisplayController.INSTANCE.get(context).setPriorityListener(
                 (displayContext, info, flags) -> {
                     if ((flags & (CHANGE_DENSITY | CHANGE_SUPPORTED_BOUNDS
@@ -252,8 +256,12 @@
                     }
                 });
         if (Flags.oneGridSpecs()) {
-            mLandscapeModePreferenceListener = (String s) -> {
-                if (isFixedLandscape != FIXED_LANDSCAPE_MODE.get(context)) {
+            mLandscapeModePreferenceListener = (String preference_name) -> {
+                // Here we need both conditions even though they might seem redundant but because
+                // the update happens in the executable there can be race conditions and this avoids
+                // it.
+                if (isFixedLandscape != FIXED_LANDSCAPE_MODE.get(context)
+                        && SettingsActivity.FIXED_LANDSCAPE_MODE.equals(preference_name)) {
                     MAIN_EXECUTOR.execute(() -> {
                         Trace.beginSection("InvariantDeviceProfile#setFixedLandscape");
                         onConfigChanged(context.getApplicationContext());
@@ -346,6 +354,11 @@
     }
 
     private String initGrid(Context context, String gridName) {
+        FileLog.d(TAG, "Before initGrid:"
+                + "gridName:" + gridName
+                + ", dbFile:" + dbFile
+                + ", LauncherPrefs GRID_NAME:" + LauncherPrefs.get(context).get(GRID_NAME)
+                + ", LauncherPrefs DB_FILE:" + LauncherPrefs.get(context).get(DB_FILE));
         Info displayInfo = DisplayController.INSTANCE.get(context).getInfo();
         List<DisplayOption> allOptions = getPredefinedDeviceProfiles(
                 context,
@@ -371,6 +384,11 @@
         }
 
         initGrid(context, displayInfo, displayOption);
+        FileLog.d(TAG, "After initGrid:"
+                + "gridName:" + gridName
+                + ", dbFile:" + dbFile
+                + ", LauncherPrefs GRID_NAME:" + LauncherPrefs.get(context).get(GRID_NAME)
+                + ", LauncherPrefs DB_FILE:" + LauncherPrefs.get(context).get(DB_FILE));
         return displayOption.grid.name;
     }
 
@@ -386,7 +404,7 @@
      */
     @Deprecated
     public void reset(Context context) {
-        initGrid(context, getDefaultGridName(context));
+        initGrid(context, getCurrentGridName(context));
     }
 
     @VisibleForTesting
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 5b8d2fc..94b1a2a 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -58,6 +58,7 @@
 import static com.android.launcher3.LauncherConstants.TraceEvents.ON_NEW_INTENT_EVT;
 import static com.android.launcher3.LauncherConstants.TraceEvents.ON_RESUME_EVT;
 import static com.android.launcher3.LauncherConstants.TraceEvents.ON_START_EVT;
+import static com.android.launcher3.LauncherPrefs.FIXED_LANDSCAPE_MODE;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
 import static com.android.launcher3.LauncherState.ALL_APPS;
@@ -423,8 +424,6 @@
     private final SettingsCache.OnChangeListener mNaturalScrollingChangedListener =
             enabled -> mIsNaturalScrollingEnabled = enabled;
 
-    private boolean mRecreateToUpdateTheme = false;
-
     public static Launcher getLauncher(Context context) {
         return fromContext(context);
     }
@@ -787,9 +786,13 @@
             return;
         }
         // When the flag oneGridSpecs is on we want to disable ALLOW_ROTATION which is replaced
-        // by FIXED_LANDSCAPE_MODE, ALLOW_ROTATION will only be used on Tablets afterwards.
-        if (getDeviceProfile().isPhone || getDeviceProfile().isTwoPanels) {
+        // by FIXED_LANDSCAPE_MODE, ALLOW_ROTATION will only be used on Tablets and foldables
+        // afterwards.
+        if (getDeviceProfile().isPhone) {
             LauncherPrefs.get(this).put(LauncherPrefs.ALLOW_ROTATION, false);
+        } else if (getDeviceProfile().isTablet) {
+            // Tablet do not use fixed landscape mode, make sure it can't be activated by mistake
+            LauncherPrefs.get(this).put(FIXED_LANDSCAPE_MODE, false);
         }
         getRotationHelper().setFixedLandscape(
                 Objects.requireNonNull(mDeviceProfile.inv).isFixedLandscape
@@ -1679,7 +1682,7 @@
             if (FeatureFlags.enableSplitContextually()) {
                 handleSplitAnimationGoingToHome(LAUNCHER_SPLIT_SELECTION_EXIT_HOME);
             }
-            mOverlayManager.hideOverlay(isStarted() && !isForceInvisible());
+            mOverlayManager.hideOverlay(isStarted());
             handleGestureContract(intent);
         } else if (Intent.ACTION_ALL_APPS.equals(intent.getAction())) {
             showAllAppsFromIntent(alreadyOnHome);
@@ -1751,12 +1754,6 @@
     }
 
     @Override
-    protected void recreateToUpdateTheme() {
-        mRecreateToUpdateTheme = true;
-        super.recreateToUpdateTheme();
-    }
-
-    @Override
     public void onRestoreInstanceState(Bundle state) {
         super.onRestoreInstanceState(state);
         IntSet synchronouslyBoundPages = mModelCallbacks.getSynchronouslyBoundPages();
@@ -1801,8 +1798,6 @@
             outState.putParcelable(RUNTIME_STATE_PENDING_ACTIVITY_RESULT, mPendingActivityResult);
         }
 
-        outState.putBoolean(RUNTIME_STATE_RECREATE_TO_UPDATE_THEME, mRecreateToUpdateTheme);
-
         super.onSaveInstanceState(outState);
     }
 
@@ -2308,7 +2303,8 @@
             if (item.container == CONTAINER_DESKTOP) {
                 CellLayout cl = mWorkspace.getScreenWithId(presenterPos.screenId);
                 if (cl != null && cl.isOccupied(presenterPos.cellX, presenterPos.cellY)) {
-                    Object tag = cl.getChildAt(presenterPos.cellX, presenterPos.cellY).getTag();
+                    View occupiedView = cl.getChildAt(presenterPos.cellX, presenterPos.cellY);
+                    Object tag = occupiedView == null ? null : occupiedView.getTag();
                     String desc = "Collision while binding workspace item: " + item
                             + ". Collides with " + tag;
                     if (FeatureFlags.IS_STUDIO_BUILD) {
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 01d0a74..a53238d 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -20,8 +20,12 @@
 import static android.content.Context.RECEIVER_EXPORTED;
 
 import static com.android.launcher3.Flags.enableSmartspaceRemovalToggle;
+import static com.android.launcher3.InvariantDeviceProfile.GRID_NAME_PREFS_KEY;
+import static com.android.launcher3.LauncherPrefs.DB_FILE;
+import static com.android.launcher3.LauncherPrefs.GRID_NAME;
 import static com.android.launcher3.LauncherPrefs.ICON_STATE;
 import static com.android.launcher3.LauncherPrefs.THEMED_ICONS;
+import static com.android.launcher3.model.DeviceGridState.KEY_DB_FILE;
 import static com.android.launcher3.model.LoaderTask.SMARTSPACE_ON_HOME_SCREEN;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
@@ -47,6 +51,7 @@
 import com.android.launcher3.icons.IconProvider;
 import com.android.launcher3.icons.LauncherIconProvider;
 import com.android.launcher3.icons.LauncherIcons;
+import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.model.ModelLauncherCallbacks;
 import com.android.launcher3.model.WidgetsFilterDataProvider;
 import com.android.launcher3.notification.NotificationListener;
@@ -70,6 +75,7 @@
 
 public class LauncherAppState implements SafeCloseable {
 
+    public static final String TAG = "LauncherAppState";
     public static final String ACTION_FORCE_ROLOAD = "force-reload-launcher";
 
     // We do not need any synchronization for this variable as its only written on UI thread.
@@ -293,6 +299,12 @@
             if (Themes.KEY_THEMED_ICONS.equals(key)) {
                 mIconProvider.setIconThemeSupported(Themes.isThemedIconEnabled(mContext));
                 verifyIconChanged();
+            } else if (GRID_NAME_PREFS_KEY.equals(key)) {
+                FileLog.d(TAG, "onPrefChanged GRID_NAME changed: "
+                        + LauncherPrefs.get(mContext).get(GRID_NAME));
+            } else if (KEY_DB_FILE.equals(key)) {
+                FileLog.d(TAG, "onPrefChanged DB_FILE changed: "
+                        + LauncherPrefs.get(mContext).get(DB_FILE));
             }
         }
     }
diff --git a/src/com/android/launcher3/LauncherConstants.java b/src/com/android/launcher3/LauncherConstants.java
index 0ed239d..5cba82f 100644
--- a/src/com/android/launcher3/LauncherConstants.java
+++ b/src/com/android/launcher3/LauncherConstants.java
@@ -68,7 +68,7 @@
         // Type int[]
         static final String RUNTIME_STATE_CURRENT_SCREEN_IDS = "launcher.current_screen_ids";
         // Type: boolean
-        static final String RUNTIME_STATE_RECREATE_TO_UPDATE_THEME =
+        public static final String RUNTIME_STATE_RECREATE_TO_UPDATE_THEME =
                 "launcher.recreate_to_update_theme";
     }
 }
diff --git a/src/com/android/launcher3/LauncherPrefs.kt b/src/com/android/launcher3/LauncherPrefs.kt
index 712c56c..5b9c2fa 100644
--- a/src/com/android/launcher3/LauncherPrefs.kt
+++ b/src/com/android/launcher3/LauncherPrefs.kt
@@ -20,6 +20,7 @@
 import android.content.SharedPreferences
 import androidx.annotation.VisibleForTesting
 import com.android.launcher3.BuildConfig.WIDGET_ON_FIRST_SCREEN
+import com.android.launcher3.InvariantDeviceProfile.GRID_NAME_PREFS_KEY
 import com.android.launcher3.LauncherFiles.DEVICE_PREFERENCES_KEY
 import com.android.launcher3.LauncherFiles.SHARED_PREFERENCES_KEY
 import com.android.launcher3.model.DeviceGridState
@@ -138,7 +139,7 @@
         @JvmField
         val GRID_NAME =
             ConstantItem(
-                "idp_grid_name",
+                GRID_NAME_PREFS_KEY,
                 isBackedUp = true,
                 defaultValue = null,
                 encryptionType = EncryptionType.ENCRYPTED,
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 0ec3b79..b05a46d 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -903,12 +903,14 @@
     @Override
     public void onViewAdded(View child) {
         super.onViewAdded(child);
+        mPageScrolls = null;
         dispatchPageCountChanged();
     }
 
     @Override
     public void onViewRemoved(View child) {
         super.onViewRemoved(child);
+        mPageScrolls = null;
         runOnPageScrollsInitialized(() -> {
             mCurrentPage = validateNewPage(mCurrentPage);
             mCurrentScrollOverPage = mCurrentPage;
diff --git a/src/com/android/launcher3/ShortcutAndWidgetContainer.java b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
index a8733f2..180ed87 100644
--- a/src/com/android/launcher3/ShortcutAndWidgetContainer.java
+++ b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
@@ -330,8 +330,13 @@
         mTranslationProvider = provider;
     }
 
+    /** Returns the current {@link TranslationProvider translation provider}. */
+    public @Nullable TranslationProvider getTranslationProvider() {
+        return mTranslationProvider;
+    }
+
     /** Provides translation values to apply when laying out child views. */
-    interface TranslationProvider {
+    public interface TranslationProvider {
         float getTranslationX(int cellX);
     }
 }
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 71a2589..9060691 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -16,8 +16,6 @@
 
 package com.android.launcher3;
 
-import static android.graphics.drawable.AdaptiveIconDrawable.getExtraInsetFraction;
-
 import static com.android.launcher3.BuildConfig.WIDGET_ON_FIRST_SCREEN;
 import static com.android.launcher3.Flags.enableSmartspaceAsAWidget;
 import static com.android.launcher3.icons.BitmapInfo.FLAG_THEMED;
@@ -36,9 +34,6 @@
 import android.content.pm.ShortcutInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.BlendMode;
-import android.graphics.BlendModeColorFilter;
 import android.graphics.Color;
 import android.graphics.ColorFilter;
 import android.graphics.LightingColorFilter;
@@ -49,10 +44,8 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.drawable.AdaptiveIconDrawable;
-import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
-import android.graphics.drawable.InsetDrawable;
 import android.os.Build;
 import android.os.Build.VERSION_CODES;
 import android.os.DeadObjectException;
@@ -85,7 +78,6 @@
 import com.android.launcher3.icons.BitmapInfo;
 import com.android.launcher3.icons.CacheableShortcutInfo;
 import com.android.launcher3.icons.LauncherIcons;
-import com.android.launcher3.icons.ThemedIconDrawable;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.ItemInfoWithIcon;
 import com.android.launcher3.pm.ShortcutConfigActivityInfo;
@@ -639,7 +631,7 @@
         Drawable mainIcon = null;
 
         Drawable badge = null;
-        if ((info instanceof ItemInfoWithIcon iiwi) && !iiwi.usingLowResIcon()) {
+        if ((info instanceof ItemInfoWithIcon iiwi) && !iiwi.getMatchingLookupFlag().useLowRes()) {
             badge = iiwi.bitmap.getBadgeDrawable(context, useTheme);
         }
 
@@ -696,29 +688,19 @@
             return null;
         }
 
-        // Inject monochrome icon drawable
+        // Inject theme icon drawable
         if (ATLEAST_T && useTheme) {
-            result.mutate();
-            int[] colors = ThemedIconDrawable.getColors(context);
-            Drawable mono = result.getMonochrome();
-
-            if (mono != null) {
-                mono.setTint(colors[1]);
-            } else  if (info instanceof ItemInfoWithIcon iiwi) {
-                // Inject a previously generated monochrome icon
-                Bitmap monoBitmap = iiwi.bitmap.getMono();
-                if (monoBitmap != null) {
-                    // Use BitmapDrawable instead of FastBitmapDrawable so that the colorState is
-                    // preserved in constantState
-                    mono = new BitmapDrawable(monoBitmap);
-                    mono.setColorFilter(new BlendModeColorFilter(colors[1], BlendMode.SRC_IN));
-                    // Inset the drawable according to the AdaptiveIconDrawable layers
-                    mono = new InsetDrawable(mono, getExtraInsetFraction() / 2);
+            try (LauncherIcons li = LauncherIcons.obtain(context)) {
+                if (li.getThemeController() != null) {
+                    AdaptiveIconDrawable themed = li.getThemeController().createThemedAdaptiveIcon(
+                            context,
+                            result,
+                            info instanceof ItemInfoWithIcon iiwi ? iiwi.bitmap : null);
+                    if (themed != null) {
+                        result = themed;
+                    }
                 }
             }
-            if (mono != null) {
-                result = new AdaptiveIconDrawable(new ColorDrawable(colors[0]), mono);
-            }
         }
 
         if (badge == null) {
diff --git a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
index 8505a6d..b0001af 100644
--- a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
@@ -761,6 +761,22 @@
         }
     }
 
+    @Override
+    public void addChildrenForAccessibility(ArrayList<View> arrayList) {
+        super.addChildrenForAccessibility(arrayList);
+        if (!Flags.floatingSearchBar()) {
+            // Searchbox container is visually at the top of the all apps UI but it's present in
+            // end of the children list.
+            // We need to move the searchbox to the top in a11y tree for a11y services to read the
+            // all apps screen in same as visual order.
+            arrayList.stream().filter(v -> v.getId() == R.id.search_container_all_apps)
+                    .findFirst().ifPresent(v -> {
+                        arrayList.remove(v);
+                        arrayList.add(0, v);
+                    });
+        }
+    }
+
     protected void updateHeaderScroll(int scrolledOffset) {
         float prog1 = Utilities.boundToRange((float) scrolledOffset / mHeaderThreshold, 0f, 1f);
         int headerColor = getHeaderColor(prog1);
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 8554de5..4cc31d2 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -34,6 +34,7 @@
 import static com.android.launcher3.util.SystemUiController.UI_STATE_ALL_APPS;
 
 import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
 import android.util.FloatProperty;
 import android.view.HapticFeedbackConstants;
@@ -365,6 +366,12 @@
         Interpolator verticalProgressInterpolator = config.getInterpolator(ANIM_VERTICAL_PROGRESS,
                 config.isUserControlled() ? LINEAR : DECELERATE_1_7);
         Animator anim = createSpringAnimation(mProgress, targetProgress);
+        anim.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                setProgress(targetProgress);
+            }
+        });
         anim.setInterpolator(verticalProgressInterpolator);
         builder.add(anim);
 
diff --git a/src/com/android/launcher3/allapps/WorkProfileManager.java b/src/com/android/launcher3/allapps/WorkProfileManager.java
index 6ebab5a..6d7d193 100644
--- a/src/com/android/launcher3/allapps/WorkProfileManager.java
+++ b/src/com/android/launcher3/allapps/WorkProfileManager.java
@@ -148,7 +148,7 @@
         if (getAH() != null) {
             getAH().applyPadding();
         }
-        mWorkUtilityView.setOnClickListener(this::onWorkFabClicked);
+        mWorkUtilityView.getWorkFAB().setOnClickListener(this::onWorkFabClicked);
         return true;
     }
     /**
diff --git a/src/com/android/launcher3/allapps/WorkUtilityView.java b/src/com/android/launcher3/allapps/WorkUtilityView.java
index 5949b78..bccc279 100644
--- a/src/com/android/launcher3/allapps/WorkUtilityView.java
+++ b/src/com/android/launcher3/allapps/WorkUtilityView.java
@@ -25,6 +25,7 @@
 import android.graphics.Rect;
 import android.text.TextUtils;
 import android.util.AttributeSet;
+import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowInsets;
 import android.widget.ImageButton;
@@ -83,6 +84,7 @@
     // Threshold when user scrolls up/down to determine when should button extend/collapse
     private final int mScrollThreshold;
     private ValueAnimator mPauseFABAnim;
+    private View mWorkFAB;
     private TextView mPauseText;
     private ImageView mWorkIcon;
     private ImageButton mSchedulerButton;
@@ -117,6 +119,7 @@
 
         mPauseText = findViewById(R.id.pause_text);
         mWorkIcon = findViewById(R.id.work_icon);
+        mWorkFAB = findViewById(R.id.work_mode_toggle);
         mSchedulerButton = findViewById(R.id.work_scheduler);
         setSelected(true);
         KeyboardInsetAnimationCallback keyboardInsetAnimationCallback =
@@ -131,7 +134,8 @@
         if (shouldUseScheduler()) {
             mSchedulerButton.setVisibility(VISIBLE);
             mSchedulerButton.setOnClickListener(view ->
-                    mContext.startActivity(new Intent(mWorkSchedulerIntentAction)));
+                    mActivityContext.startActivitySafely(view,
+                            new Intent(mWorkSchedulerIntentAction), null /* itemInfo */));
         }
     }
 
@@ -388,6 +392,10 @@
         return mScrollThreshold;
     }
 
+    public View getWorkFAB() {
+        return mWorkFAB;
+    }
+
     public void updateStringFromCache(){
         StringCache cache = mActivityContext.getStringCache();
         if (cache != null) {
diff --git a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
index 1e3df1e..340fb02 100644
--- a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
+++ b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
@@ -32,6 +32,7 @@
 import com.android.launcher3.util.SettingsCache;
 import com.android.launcher3.util.VibratorWrapper;
 import com.android.launcher3.util.window.RefreshRateTracker;
+import com.android.launcher3.util.window.WindowManagerProxy;
 import com.android.launcher3.widget.custom.CustomWidgetManager;
 
 import dagger.BindsInstance;
@@ -60,6 +61,7 @@
     PluginManagerWrapper getPluginManagerWrapper();
     VibratorWrapper getVibratorWrapper();
     MSDLPlayerWrapper getMSDLPlayerWrapper();
+    WindowManagerProxy getWmProxy();
 
     /** Builder for LauncherBaseAppComponent. */
     interface Builder {
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index a24f3ff..41b42b6 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -50,6 +50,7 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.ShortcutAndWidgetContainer;
+import com.android.launcher3.ShortcutAndWidgetContainer.TranslationProvider;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.anim.PendingAnimation;
@@ -69,7 +70,9 @@
 public class DragLayer extends BaseDragLayer<Launcher> implements LauncherOverlayCallbacks {
 
     public static final int ALPHA_INDEX_OVERLAY = 0;
-    private static final int ALPHA_CHANNEL_COUNT = 1;
+
+    public static final int ALPHA_INDEX_LOADER = 1;
+    private static final int ALPHA_CHANNEL_COUNT = 2;
 
     public static final int ANIMATION_END_DISAPPEAR = 0;
     public static final int ANIMATION_END_REMAIN_VISIBLE = 2;
@@ -246,23 +249,27 @@
     public void animateViewIntoPosition(DragView dragView, final View child, int duration,
             View anchorView) {
 
-        ShortcutAndWidgetContainer parentChildren = (ShortcutAndWidgetContainer) child.getParent();
+        ShortcutAndWidgetContainer childParent = (ShortcutAndWidgetContainer) child.getParent();
         CellLayoutLayoutParams lp =  (CellLayoutLayoutParams) child.getLayoutParams();
-        parentChildren.measureChild(child);
-        parentChildren.layoutChild(child);
+        childParent.measureChild(child);
+        childParent.layoutChild(child);
 
         float coord[] = new float[2];
         float childScale = child.getScaleX();
 
         coord[0] = lp.x + (child.getMeasuredWidth() * (1 - childScale) / 2);
         coord[1] = lp.y + (child.getMeasuredHeight() * (1 - childScale) / 2);
+        TranslationProvider translationProvider = childParent.getTranslationProvider();
+        if (translationProvider != null) {
+            coord[0] = coord[0] + translationProvider.getTranslationX(lp.getCellX());
+        }
 
         // Since the child hasn't necessarily been laid out, we force the lp to be updated with
         // the correct coordinates (above) and use these to determine the final location
         float scale = getDescendantCoordRelativeToSelf((View) child.getParent(), coord);
 
         // We need to account for the scale of the child itself, as the above only accounts for
-        // for the scale in parents.
+        // the scale in parents.
         scale *= childScale;
         int toX = Math.round(coord[0]);
         int toY = Math.round(coord[1]);
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index bcee442..67fe889 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -224,7 +224,6 @@
         measure(makeMeasureSpec(width, EXACTLY), makeMeasureSpec(height, EXACTLY));
 
         mBlurSizeOutline = getResources().getDimensionPixelSize(R.dimen.blur_size_medium_outline);
-        setElevation(getResources().getDimension(R.dimen.drag_elevation));
         setWillNotDraw(false);
     }
 
diff --git a/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java b/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java
deleted file mode 100644
index bebef70..0000000
--- a/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2010 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.dragndrop;
-
-import com.android.launcher3.Alarm;
-import com.android.launcher3.CellLayout;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.OnAlarmListener;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.Workspace;
-
-public class SpringLoadedDragController implements OnAlarmListener {
-    // how long the user must hover over a mini-screen before it unshrinks
-    private static final long ENTER_SPRING_LOAD_HOVER_TIME = 500;
-    private static final long ENTER_SPRING_LOAD_HOVER_TIME_IN_TEST = 3000;
-    private static final long ENTER_SPRING_LOAD_CANCEL_HOVER_TIME = 950;
-
-    Alarm mAlarm;
-
-    // the screen the user is currently hovering over, if any
-    private CellLayout mScreen;
-    private Launcher mLauncher;
-
-    public SpringLoadedDragController(Launcher launcher) {
-        mLauncher = launcher;
-        mAlarm = new Alarm();
-        mAlarm.setOnAlarmListener(this);
-    }
-
-    private long getEnterSpringLoadHoverTime() {
-        // Some TAPL tests are flaky on Cuttlefish with a low waiting time
-        return Utilities.isRunningInTestHarness()
-                ? ENTER_SPRING_LOAD_HOVER_TIME_IN_TEST
-                : ENTER_SPRING_LOAD_HOVER_TIME;
-    }
-
-    public void cancel() {
-        mAlarm.cancelAlarm();
-    }
-
-    // Set a new alarm to expire for the screen that we are hovering over now
-    public void setAlarm(CellLayout cl) {
-        mAlarm.cancelAlarm();
-        mAlarm.setAlarm((cl == null) ? ENTER_SPRING_LOAD_CANCEL_HOVER_TIME
-                : getEnterSpringLoadHoverTime());
-        mScreen = cl;
-    }
-
-    // this is called when our timer runs out
-    public void onAlarm(Alarm alarm) {
-        if (mScreen != null) {
-            // Snap to the screen that we are hovering over now
-            Workspace<?> w = mLauncher.getWorkspace();
-            if (!w.isVisible(mScreen)) {
-                w.snapToPage(w.indexOfChild(mScreen));
-            }
-        } else {
-            mLauncher.getDragController().cancelDrag();
-        }
-    }
-}
diff --git a/src/com/android/launcher3/dragndrop/SpringLoadedDragController.kt b/src/com/android/launcher3/dragndrop/SpringLoadedDragController.kt
new file mode 100644
index 0000000..4cbe7bb
--- /dev/null
+++ b/src/com/android/launcher3/dragndrop/SpringLoadedDragController.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2010 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.dragndrop
+
+import com.android.launcher3.Alarm
+import com.android.launcher3.CellLayout
+import com.android.launcher3.Launcher
+import com.android.launcher3.OnAlarmListener
+import com.android.launcher3.Utilities
+
+class SpringLoadedDragController(private val launcher: Launcher) : OnAlarmListener {
+    internal val alarm = Alarm().also { it.setOnAlarmListener(this) }
+
+    // the screen the user is currently hovering over, if any
+    private var screen: CellLayout? = null
+
+    fun cancel() = alarm.cancelAlarm()
+
+    // Set a new alarm to expire for the screen that we are hovering over now
+    fun setAlarm(cl: CellLayout?) {
+        cancel()
+        alarm.setAlarm(
+            when {
+                cl == null -> ENTER_SPRING_LOAD_CANCEL_HOVER_TIME
+                // Some TAPL tests are flaky on Cuttlefish with a low waiting time
+                Utilities.isRunningInTestHarness() -> ENTER_SPRING_LOAD_HOVER_TIME_IN_TEST
+                else -> ENTER_SPRING_LOAD_HOVER_TIME
+            }
+        )
+        screen = cl
+    }
+
+    // this is called when our timer runs out
+    override fun onAlarm(alarm: Alarm) {
+        if (screen != null) {
+            // Snap to the screen that we are hovering over now
+            with(launcher.workspace) {
+                if (!isVisible(screen) && launcher.dragController.mDistanceSinceScroll != 0) {
+                    snapToPage(indexOfChild(screen))
+                }
+            }
+        } else {
+            launcher.dragController.cancelDrag()
+        }
+    }
+
+    companion object {
+        // how long the user must hover over a mini-screen before it unshrinks
+        private const val ENTER_SPRING_LOAD_HOVER_TIME: Long = 500
+        private const val ENTER_SPRING_LOAD_HOVER_TIME_IN_TEST: Long = 3000
+        private const val ENTER_SPRING_LOAD_CANCEL_HOVER_TIME: Long = 950
+    }
+}
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index e7c4024..ab4105c 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -17,6 +17,7 @@
 package com.android.launcher3.icons;
 
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
+import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 import static com.android.launcher3.widget.WidgetSections.NO_CATEGORY;
@@ -51,6 +52,7 @@
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.icons.cache.BaseIconCache;
+import com.android.launcher3.icons.cache.CacheLookupFlag;
 import com.android.launcher3.icons.cache.CachedObject;
 import com.android.launcher3.icons.cache.CachedObjectCachingLogic;
 import com.android.launcher3.icons.cache.LauncherActivityCachingLogic;
@@ -163,12 +165,12 @@
         Supplier<ItemInfoWithIcon> task;
         if (info instanceof AppInfo || info instanceof WorkspaceItemInfo) {
             task = () -> {
-                getTitleAndIcon(info, false);
+                getTitleAndIcon(info, DEFAULT_LOOKUP_FLAG);
                 return info;
             };
         } else if (info instanceof PackageItemInfo pii) {
             task = () -> {
-                getTitleAndIconForApp(pii, false);
+                getTitleAndIconForApp(pii, DEFAULT_LOOKUP_FLAG);
                 return pii;
             };
         } else {
@@ -207,7 +209,7 @@
     public synchronized void updateTitleAndIcon(AppInfo application) {
         CacheEntry entry = cacheLocked(application.componentName,
                 application.user, () -> null, LauncherActivityCachingLogic.INSTANCE,
-                application.usingLowResIcon() ? LookupFlag.USE_LOW_RES : LookupFlag.DEFAULT);
+                application.getMatchingLookupFlag());
         if (entry.bitmap != null || !isDefaultIcon(entry.bitmap, application.user)) {
             applyCacheEntry(entry, application);
         }
@@ -218,11 +220,11 @@
      */
     @SuppressWarnings("NewApi")
     public synchronized void getTitleAndIcon(ItemInfoWithIcon info,
-            LauncherActivityInfo activityInfo, boolean useLowResIcon) {
+            LauncherActivityInfo activityInfo, @NonNull CacheLookupFlag lookupFlag) {
         boolean isAppArchived = Flags.enableSupportForArchiving() && activityInfo != null
                 && activityInfo.getActivityInfo().isArchived;
         // If we already have activity info, no need to use package icon
-        getTitleAndIcon(info, () -> activityInfo, isAppArchived, useLowResIcon);
+        getTitleAndIcon(info, () -> activityInfo, lookupFlag.withUsePackageIcon(isAppArchived));
     }
 
     /**
@@ -252,7 +254,7 @@
                 user,
                 () -> si,
                 CacheableShortcutCachingLogic.INSTANCE,
-                LookupFlag.SKIP_ADD_TO_MEM_CACHE).bitmap;
+                DEFAULT_LOOKUP_FLAG.withSkipAddToMemCache()).bitmap;
         if (bitmapInfo.isNullOrLowRes()) {
             bitmapInfo = getDefaultIcon(user);
         }
@@ -291,12 +293,12 @@
                 appInfo.intent = new Intent(Intent.ACTION_MAIN)
                         .addCategory(Intent.CATEGORY_LAUNCHER)
                         .setComponent(cn);
-                getTitleAndIcon(appInfo, false);
+                getTitleAndIcon(appInfo, DEFAULT_LOOKUP_FLAG);
                 return appInfo;
             }
         }
         PackageItemInfo pkgInfo = new PackageItemInfo(pkg, shortcutInfo.getUserHandle());
-        getTitleAndIconForApp(pkgInfo, false);
+        getTitleAndIconForApp(pkgInfo, DEFAULT_LOOKUP_FLAG);
         return pkgInfo;
     }
 
@@ -304,7 +306,9 @@
      * Fill in {@param info} with the icon and label. If the
      * corresponding activity is not found, it reverts to the package icon.
      */
-    public synchronized void getTitleAndIcon(ItemInfoWithIcon info, boolean useLowResIcon) {
+    public synchronized void getTitleAndIcon(
+            @NonNull ItemInfoWithIcon info,
+            @NonNull CacheLookupFlag lookupFlag) {
         // null info means not installed, but if we have a component from the intent then
         // we should still look in the cache for restored app icons.
         if (info.getTargetComponent() == null) {
@@ -314,7 +318,7 @@
         } else {
             Intent intent = info.getIntent();
             getTitleAndIcon(info, () -> mLauncherApps.resolveActivity(intent, info.user),
-                    true, useLowResIcon);
+                    lookupFlag.withUsePackageIcon());
         }
     }
 
@@ -324,7 +328,7 @@
     public synchronized String getTitleNoCache(CachedObject info) {
         CacheEntry entry = cacheLocked(info.getComponent(), info.getUser(), () -> info,
                 CachedObjectCachingLogic.INSTANCE,
-                LookupFlag.USE_LOW_RES | LookupFlag.SKIP_ADD_TO_MEM_CACHE);
+                DEFAULT_LOOKUP_FLAG.withUseLowRes().withSkipAddToMemCache());
         return Utilities.trim(entry.title);
     }
 
@@ -334,12 +338,9 @@
     public synchronized void getTitleAndIcon(
             @NonNull ItemInfoWithIcon infoInOut,
             @NonNull Supplier<LauncherActivityInfo> activityInfoProvider,
-            boolean usePkgIcon, boolean useLowResIcon) {
-        int lookupFlags = LookupFlag.DEFAULT;
-        if (usePkgIcon) lookupFlags |= LookupFlag.USE_PACKAGE_ICON;
-        if (useLowResIcon) lookupFlags |= LookupFlag.USE_LOW_RES;
+            @NonNull CacheLookupFlag lookupFlag) {
         CacheEntry entry = cacheLocked(infoInOut.getTargetComponent(), infoInOut.user,
-                activityInfoProvider, LauncherActivityCachingLogic.INSTANCE, lookupFlags);
+                activityInfoProvider, LauncherActivityCachingLogic.INSTANCE, lookupFlag);
         applyCacheEntry(entry, infoInOut);
     }
 
@@ -441,7 +442,7 @@
                                 /* user = */ sectionKey.first,
                                 () -> duplicateIconRequests.get(0).launcherActivityInfo,
                                 LauncherActivityCachingLogic.INSTANCE,
-                                sectionKey.second ? LookupFlag.USE_LOW_RES : LookupFlag.DEFAULT,
+                                DEFAULT_LOOKUP_FLAG.withUseLowRes(sectionKey.second),
                                 c);
 
                         for (IconRequestInfo<T> iconRequest : duplicateIconRequests) {
@@ -515,9 +516,10 @@
      * Fill in {@param infoInOut} with the corresponding icon and label.
      */
     public synchronized void getTitleAndIconForApp(
-            @NonNull final PackageItemInfo infoInOut, final boolean useLowResIcon) {
+            @NonNull final PackageItemInfo infoInOut,
+            @NonNull CacheLookupFlag lookupFlag) {
         CacheEntry entry = getEntryForPackageLocked(
-                infoInOut.packageName, infoInOut.user, useLowResIcon);
+                infoInOut.packageName, infoInOut.user, lookupFlag.useLowRes());
         applyCacheEntry(entry, infoInOut);
         if (infoInOut.widgetCategory == NO_CATEGORY) {
             return;
diff --git a/src/com/android/launcher3/icons/LauncherIcons.java b/src/com/android/launcher3/icons/LauncherIcons.java
index 884d448..839dfb7 100644
--- a/src/com/android/launcher3/icons/LauncherIcons.java
+++ b/src/com/android/launcher3/icons/LauncherIcons.java
@@ -17,15 +17,13 @@
 package com.android.launcher3.icons;
 
 import android.content.Context;
-import android.graphics.drawable.AdaptiveIconDrawable;
-import android.graphics.drawable.Drawable;
 import android.os.UserHandle;
 
 import androidx.annotation.NonNull;
 
-import com.android.launcher3.Flags;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.graphics.IconShape;
+import com.android.launcher3.icons.mono.MonoIconThemeController;
 import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.SafeCloseable;
@@ -57,13 +55,13 @@
 
     private final ConcurrentLinkedQueue<LauncherIcons> mPool;
 
-    private MonochromeIconFactory mMonochromeIconFactory;
-
     protected LauncherIcons(Context context, int fillResIconDpi, int iconBitmapSize,
             ConcurrentLinkedQueue<LauncherIcons> pool) {
         super(context, fillResIconDpi, iconBitmapSize,
                 IconShape.INSTANCE.get(context).getShape().enableShapeDetection());
-        mMonoIconEnabled = Themes.isThemedIconEnabled(context);
+        if (Themes.isThemedIconEnabled(context)) {
+            mThemeController = new MonoIconThemeController();
+        }
         mPool = pool;
     }
 
@@ -75,18 +73,6 @@
         mPool.add(this);
     }
 
-    @Override
-    protected Drawable getMonochromeDrawable(AdaptiveIconDrawable base) {
-        Drawable mono = super.getMonochromeDrawable(base);
-        if (mono != null || !Flags.forceMonochromeAppIcons()) {
-            return mono;
-        }
-        if (mMonochromeIconFactory == null) {
-            mMonochromeIconFactory = new MonochromeIconFactory(mIconBitmapSize);
-        }
-        return mMonochromeIconFactory.wrap(base);
-    }
-
     @NonNull
     @Override
     protected UserIconInfo getUserInfo(@NonNull UserHandle user) {
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index dbab52a..6eb02ab 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -301,6 +301,12 @@
         @UiEvent(doc = "User swipes or fling in RIGHT direction on the bottom bazel area.")
         LAUNCHER_QUICKSWITCH_RIGHT(572),
 
+        @UiEvent(doc = "User swipes or fling on the bottom bazel area to enter Desktop mode.")
+        LAUNCHER_QUICKSWITCH_ENTER_DESKTOP_MODE(2025),
+
+        @UiEvent(doc = "User swipes or fling on the bottom bazel area to exit Desktop mode.")
+        LAUNCHER_QUICKSWITCH_EXIT_DESKTOP_MODE(2026),
+
         @UiEvent(doc = "User swipes or fling in DOWN direction on the bottom bazel area.")
         LAUNCHER_SWIPEDOWN_NAVBAR(573),
 
diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
index 55bcb70..b936adf 100644
--- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
+++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
@@ -194,8 +194,7 @@
                         WorkspaceItemInfo wii = (WorkspaceItemInfo) itemInfo;
                         wii.title = "";
                         wii.bitmap = cache.getDefaultIcon(item.user);
-                        cache.getTitleAndIcon(wii,
-                                ((WorkspaceItemInfo) itemInfo).usingLowResIcon());
+                        cache.getTitleAndIcon(wii, wii.getMatchingLookupFlag());
                     }
                 }
 
diff --git a/src/com/android/launcher3/model/AllAppsList.java b/src/com/android/launcher3/model/AllAppsList.java
index 7bc9273..98f9afd 100644
--- a/src/com/android/launcher3/model/AllAppsList.java
+++ b/src/com/android/launcher3/model/AllAppsList.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3.model;
 
+import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG;
 import static com.android.launcher3.model.data.AppInfo.COMPONENT_KEY_COMPARATOR;
 import static com.android.launcher3.model.data.AppInfo.EMPTY_ARRAY;
 
@@ -151,7 +152,7 @@
             return;
         }
         if (loadIcon) {
-            mIconCache.getTitleAndIcon(info, activityInfo, false /* useLowResIcon */);
+            mIconCache.getTitleAndIcon(info, activityInfo, DEFAULT_LOOKUP_FLAG);
             info.sectionName = mIndex.computeSectionName(info.title);
         } else {
             info.title = "";
@@ -177,7 +178,7 @@
         AppInfo promiseAppInfo = new AppInfo(installInfo);
 
         if (loadIcon) {
-            mIconCache.getTitleAndIcon(promiseAppInfo, promiseAppInfo.usingLowResIcon());
+            mIconCache.getTitleAndIcon(promiseAppInfo, promiseAppInfo.getMatchingLookupFlag());
             promiseAppInfo.sectionName = mIndex.computeSectionName(promiseAppInfo.title);
         } else {
             promiseAppInfo.title = "";
@@ -338,7 +339,7 @@
                 } else {
                     Intent launchIntent = AppInfo.makeLaunchIntent(info);
 
-                    mIconCache.getTitleAndIcon(applicationInfo, info, false /* useLowResIcon */);
+                    mIconCache.getTitleAndIcon(applicationInfo, info, DEFAULT_LOOKUP_FLAG);
                     applicationInfo.sectionName = mIndex.computeSectionName(applicationInfo.title);
                     applicationInfo.intent = launchIntent;
                     AppInfo.updateRuntimeFlagsForActivityTarget(applicationInfo, info,
diff --git a/src/com/android/launcher3/model/CacheDataUpdatedTask.java b/src/com/android/launcher3/model/CacheDataUpdatedTask.java
index 66b4fd9..b544b91 100644
--- a/src/com/android/launcher3/model/CacheDataUpdatedTask.java
+++ b/src/com/android/launcher3/model/CacheDataUpdatedTask.java
@@ -63,7 +63,7 @@
                 if (si.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
                         && isValidShortcut(si) && cn != null
                         && mPackages.contains(cn.getPackageName())) {
-                    iconCache.getTitleAndIcon(si, si.usingLowResIcon());
+                    iconCache.getTitleAndIcon(si, si.getMatchingLookupFlag());
                     updatedShortcuts.add(si);
                 }
             });
diff --git a/src/com/android/launcher3/model/DbEntry.kt b/src/com/android/launcher3/model/DbEntry.kt
index b79d312..da9779e 100644
--- a/src/com/android/launcher3/model/DbEntry.kt
+++ b/src/com/android/launcher3/model/DbEntry.kt
@@ -30,7 +30,6 @@
 import com.android.launcher3.LauncherSettings.Favorites.SPANY
 import com.android.launcher3.model.data.ItemInfo
 import com.android.launcher3.util.ContentWriter
-import java.net.URISyntaxException
 import java.util.Objects
 
 class DbEntry : ItemInfo(), Comparable<DbEntry> {
@@ -129,8 +128,8 @@
     private fun cleanIntentString(intentStr: String): String {
         try {
             return Intent.parseUri(intentStr, 0).apply { sourceBounds = null }.toURI()
-        } catch (e: URISyntaxException) {
-            Log.e(TAG, "Unable to parse Intent string", e)
+        } catch (e: Exception) {
+            Log.e(TAG, "Unable to parse Intent string: $intentStr", e)
             return intentStr
         }
     }
diff --git a/src/com/android/launcher3/model/ItemInstallQueue.java b/src/com/android/launcher3/model/ItemInstallQueue.java
index f9c6e96..c4f222f 100644
--- a/src/com/android/launcher3/model/ItemInstallQueue.java
+++ b/src/com/android/launcher3/model/ItemInstallQueue.java
@@ -21,6 +21,7 @@
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
+import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG;
 import static com.android.launcher3.model.data.AppInfo.makeLaunchIntent;
 import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_ARCHIVED;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
@@ -308,7 +309,8 @@
                         }
                     }
                     LauncherAppState.getInstance(context).getIconCache()
-                            .getTitleAndIcon(si, () -> lai, usePackageIcon, false);
+                            .getTitleAndIcon(si, () -> lai,
+                                    DEFAULT_LOOKUP_FLAG.withUsePackageIcon(usePackageIcon));
                     return Pair.create(si, null);
                 }
                 case ITEM_TYPE_DEEP_SHORTCUT: {
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index c01b1b6..536d4c9 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -18,6 +18,7 @@
 
 import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
 import static com.android.launcher3.Utilities.SHOULD_SHOW_FIRST_PAGE_WIDGET;
+import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG;
 
 import android.content.ComponentName;
 import android.content.ContentValues;
@@ -300,7 +301,7 @@
 
         // the fallback icon
         if (!loadIcon(info)) {
-            mIconCache.getTitleAndIcon(info, false /* useLowResIcon */);
+            mIconCache.getTitleAndIcon(info, DEFAULT_LOOKUP_FLAG);
         }
 
         if (hasRestoreFlag(WorkspaceItemInfo.FLAG_RESTORED_ICON)) {
@@ -364,7 +365,8 @@
         UserIconInfo userIconInfo = userCache.getUserInfo(user);
 
         if (loadIcon) {
-            mIconCache.getTitleAndIcon(info, mActivityInfo, useLowResIcon);
+            mIconCache.getTitleAndIcon(info, mActivityInfo,
+                    DEFAULT_LOOKUP_FLAG.withUseLowRes(useLowResIcon));
             if (mIconCache.isDefaultIcon(info.bitmap, user)) {
                 loadIcon(info);
             }
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index f96e959..4355ac4 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -25,6 +25,7 @@
 import static com.android.launcher3.LauncherPrefs.SHOULD_SHOW_SMARTSPACE;
 import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
 import static com.android.launcher3.icons.CacheableShortcutInfo.convertShortcutsToCacheableShortcuts;
+import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_HAS_SHORTCUT_PERMISSION;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_CHANGE_PERMISSION;
@@ -213,13 +214,13 @@
         ArrayList<ItemInfo> firstScreenItems = new ArrayList<>();
         filterCurrentWorkspaceItems(firstScreens, allItems, firstScreenItems,
                 new ArrayList<>() /* otherScreenItems are ignored */);
-        final int launcherBroadcastInstalledApps = Settings.Secure.getInt(
+        final int disableArchivingLauncherBroadcast = Settings.Secure.getInt(
                 mApp.getContext().getContentResolver(),
-                "launcher_broadcast_installed_apps",
-                /* def= */ 0);
+                "disable_launcher_broadcast_installed_apps",
+                /* default */ 0);
         boolean shouldAttachArchivingExtras = mIsRestoreFromBackup
-                && (launcherBroadcastInstalledApps == 1
-                        || Flags.enableFirstScreenBroadcastArchivingExtras());
+                && disableArchivingLauncherBroadcast == 0
+                && Flags.enableFirstScreenBroadcastArchivingExtras();
         if (shouldAttachArchivingExtras) {
             List<FirstScreenBroadcastModel> broadcastModels =
                     FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast(
@@ -231,6 +232,7 @@
             logASplit("Sending first screen broadcast with additional archiving Extras");
             FirstScreenBroadcastHelper.sendBroadcastsForModels(mApp.getContext(), broadcastModels);
         } else {
+            logASplit("Sending first screen broadcast");
             mFirstScreenBroadcast.sendBroadcasts(mApp.getContext(), firstScreenItems);
         }
     }
@@ -261,6 +263,9 @@
             // sanitizeData should not be invoked if the workspace is loaded from a db different
             // from the main db as defined in the invariant device profile.
             // (e.g. both grid preview and minimal device mode uses a different db)
+            // TODO(b/384731096): Write Unit Test to make sure sanitizeWidgetsShortcutsAndPackages
+            //  actually re-pins shortcuts that are in model but not in ShortcutManager, if possible
+            //  after a simulated restore.
             if (Objects.equals(mApp.getInvariantDeviceProfile().dbFile, mDbName)) {
                 verifyNotStopped();
                 sanitizeFolders(mItemsDeleted);
@@ -276,7 +281,6 @@
             mModelDelegate.workspaceLoadComplete();
             // Notify the installer packages of packages with active installs on the first screen.
             sendFirstScreenActiveInstallsBroadcast();
-            logASplit("sendFirstScreenBroadcast finished");
 
             // Take a break
             waitForIdle();
@@ -599,10 +603,10 @@
                 info.rank = rank;
 
                 if (info instanceof WorkspaceItemInfo wii
-                        && wii.usingLowResIcon()
+                        && wii.getMatchingLookupFlag().useLowRes()
                         && wii.itemType == Favorites.ITEM_TYPE_APPLICATION
                         && verifiers.stream().anyMatch(it -> it.isItemInPreview(info.rank))) {
-                    mIconCache.getTitleAndIcon(wii, false);
+                    mIconCache.getTitleAndIcon(wii, DEFAULT_LOOKUP_FLAG);
                 } else if (info instanceof AppPairInfo api) {
                     api.fetchHiResIconsIfNeeded(mIconCache);
                 }
@@ -765,7 +769,7 @@
                     iconRequestInfos.add(new IconRequestInfo<>(
                             promiseAppInfo,
                             /* launcherActivityInfo= */ null,
-                            promiseAppInfo.usingLowResIcon()));
+                            promiseAppInfo.getMatchingLookupFlag().useLowRes()));
                 }
             }
         }
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index 5464afe..d619965 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -326,7 +326,8 @@
                                     itemInfo.setNonResizeable(ApiWrapper.INSTANCE.get(context)
                                             .isNonResizeableActivity(activities.get(0)));
                                 }
-                                iconCache.getTitleAndIcon(itemInfo, itemInfo.usingLowResIcon());
+                                iconCache.getTitleAndIcon(
+                                        itemInfo, itemInfo.getMatchingLookupFlag());
                                 infoUpdated = true;
                             }
                         }
diff --git a/src/com/android/launcher3/model/SessionFailureTask.kt b/src/com/android/launcher3/model/SessionFailureTask.kt
index 0d006fa..8baf568 100644
--- a/src/com/android/launcher3/model/SessionFailureTask.kt
+++ b/src/com/android/launcher3/model/SessionFailureTask.kt
@@ -48,7 +48,7 @@
                 for (info in dataModel.itemsIdMap) {
                     if (info is WorkspaceItemInfo && info.isArchived && user == info.user) {
                         // Refresh icons on the workspace for archived apps.
-                        iconCache.getTitleAndIcon(info, info.usingLowResIcon())
+                        iconCache.getTitleAndIcon(info, info.matchingLookupFlag)
                         updatedItems.add(info)
                     }
                 }
diff --git a/src/com/android/launcher3/model/WidgetItem.java b/src/com/android/launcher3/model/WidgetItem.java
index e757a68..0caeb8d 100644
--- a/src/com/android/launcher3/model/WidgetItem.java
+++ b/src/com/android/launcher3/model/WidgetItem.java
@@ -1,19 +1,8 @@
 package com.android.launcher3.model;
 
-import static android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN;
-import static android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD;
-import static android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX;
-
-import android.annotation.SuppressLint;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
-import android.content.res.Resources;
-import android.util.SparseArray;
-import android.widget.RemoteViews;
 
-import androidx.core.os.BuildCompat;
-
-import com.android.launcher3.Flags;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.icons.BitmapInfo;
@@ -21,7 +10,6 @@
 import com.android.launcher3.pm.ShortcutConfigActivityInfo;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
-import com.android.launcher3.widget.WidgetManagerHelper;
 
 /**
  * An wrapper over various items displayed in a widget picker,
@@ -37,11 +25,9 @@
     public final String label;
     public final CharSequence description;
     public final int spanX, spanY;
-    public final SparseArray<RemoteViews> generatedPreviews;
 
     public WidgetItem(LauncherAppWidgetProviderInfo info,
-            InvariantDeviceProfile idp, IconCache iconCache, Context context,
-            WidgetManagerHelper helper) {
+            InvariantDeviceProfile idp, IconCache iconCache, Context context) {
         super(info.provider, info.getProfile());
 
         label = iconCache.getTitleNoCache(info);
@@ -51,27 +37,6 @@
 
         spanX = Math.min(info.spanX, idp.numColumns);
         spanY = Math.min(info.spanY, idp.numRows);
-
-        if (BuildCompat.isAtLeastV() && Flags.enableGeneratedPreviews()) {
-            generatedPreviews = new SparseArray<>(3);
-            for (int widgetCategory : new int[] {
-                    WIDGET_CATEGORY_HOME_SCREEN,
-                    WIDGET_CATEGORY_KEYGUARD,
-                    WIDGET_CATEGORY_SEARCHBOX,
-            }) {
-                if ((widgetCategory & widgetInfo.generatedPreviewCategories) != 0) {
-                    generatedPreviews.put(widgetCategory,
-                            helper.loadGeneratedPreview(widgetInfo, widgetCategory));
-                }
-            }
-        } else {
-            generatedPreviews = null;
-        }
-    }
-
-    public WidgetItem(LauncherAppWidgetProviderInfo info,
-            InvariantDeviceProfile idp, IconCache iconCache, Context context) {
-        this(info, idp, iconCache, context, new WidgetManagerHelper(context));
     }
 
     public WidgetItem(ShortcutConfigActivityInfo info, IconCache iconCache) {
@@ -82,7 +47,6 @@
         widgetInfo = null;
         activityInfo = info;
         spanX = spanY = 1;
-        generatedPreviews = null;
     }
 
     /**
@@ -101,26 +65,8 @@
         return false;
     }
 
-    /** Returns whether this {@link WidgetItem} has a preview layout that can be used. */
-    @SuppressLint("NewApi") // Already added API check.
-    public boolean hasPreviewLayout() {
-        return widgetInfo != null && widgetInfo.previewLayout != Resources.ID_NULL;
-    }
-
     /** Returns whether this {@link WidgetItem} is for a shortcut rather than an app widget. */
     public boolean isShortcut() {
         return activityInfo != null;
     }
-
-    /**
-     * Returns whether this {@link WidgetItem} has a generated preview for the given widget
-     * category.
-     */
-    public boolean hasGeneratedPreview(int widgetCategory) {
-        if (!Flags.enableGeneratedPreviews() || generatedPreviews == null) {
-            return false;
-        }
-        return generatedPreviews.contains(widgetCategory)
-                && generatedPreviews.get(widgetCategory) != null;
-    }
 }
diff --git a/src/com/android/launcher3/model/WidgetsModel.java b/src/com/android/launcher3/model/WidgetsModel.java
index 01d4996..a176465 100644
--- a/src/com/android/launcher3/model/WidgetsModel.java
+++ b/src/com/android/launcher3/model/WidgetsModel.java
@@ -4,6 +4,7 @@
 import static android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_HIDE_FROM_PICKER;
 
 import static com.android.launcher3.BuildConfig.WIDGETS_ENABLED;
+import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG;
 import static com.android.launcher3.pm.ShortcutConfigActivityInfo.queryList;
 import static com.android.launcher3.widget.WidgetSections.NO_CATEGORY;
 
@@ -149,8 +150,7 @@
                         LauncherAppWidgetProviderInfo.fromProviderInfo(context, widgetInfo);
 
                 widgetsAndShortcuts.add(new WidgetItem(
-                        launcherWidgetInfo, idp, app.getIconCache(), app.getContext(),
-                        widgetManager));
+                        launcherWidgetInfo, idp, app.getIconCache(), app.getContext()));
                 updatedItems.add(launcherWidgetInfo);
             }
 
@@ -204,7 +204,7 @@
         // Update each package entry
         IconCache iconCache = app.getIconCache();
         for (PackageItemInfo p : packageItemInfoCache.values()) {
-            iconCache.getTitleAndIconForApp(p, true /* userLowResIcon */);
+            iconCache.getTitleAndIconForApp(p, DEFAULT_LOOKUP_FLAG.withUseLowRes());
         }
     }
 
@@ -213,7 +213,6 @@
         if (!WIDGETS_ENABLED) {
             return;
         }
-        WidgetManagerHelper widgetManager = new WidgetManagerHelper(app.getContext());
         for (Entry<PackageItemInfo, List<WidgetItem>> entry : mWidgetsByPackageItem.entrySet()) {
             if (packageNames.contains(entry.getKey().packageName)) {
                 List<WidgetItem> items = entry.getValue();
@@ -226,7 +225,7 @@
                         } else {
                             items.set(i, new WidgetItem(item.widgetInfo,
                                     app.getInvariantDeviceProfile(), app.getIconCache(),
-                                    app.getContext(), widgetManager));
+                                    app.getContext()));
                         }
                     }
                 }
diff --git a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
index e86b592..0272bd9 100644
--- a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
+++ b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
@@ -20,6 +20,7 @@
 import android.content.ComponentName
 import android.content.Intent
 import android.content.pm.LauncherApps
+import android.content.pm.LauncherApps.ShortcutQuery
 import android.content.pm.PackageInstaller
 import android.content.pm.ShortcutInfo
 import android.graphics.Point
@@ -33,6 +34,7 @@
 import com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RestoreError
 import com.android.launcher3.config.FeatureFlags
 import com.android.launcher3.icons.CacheableShortcutInfo
+import com.android.launcher3.icons.cache.CacheLookupFlag.Companion.DEFAULT_LOOKUP_FLAG
 import com.android.launcher3.logging.FileLog
 import com.android.launcher3.model.data.AppInfo
 import com.android.launcher3.model.data.AppPairInfo
@@ -44,6 +46,7 @@
 import com.android.launcher3.pm.PackageInstallInfo
 import com.android.launcher3.pm.UserCache
 import com.android.launcher3.shortcuts.ShortcutKey
+import com.android.launcher3.shortcuts.ShortcutRequest
 import com.android.launcher3.util.ApiWrapper
 import com.android.launcher3.util.ApplicationInfoWrapper
 import com.android.launcher3.util.ComponentKey
@@ -270,7 +273,8 @@
             c.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT -> {
                 val key = ShortcutKey.fromIntent(intent, c.user)
                 if (unlockedUsers[c.serialNumber]) {
-                    val pinnedShortcut = shortcutKeyToPinnedShortcuts[key]
+                    val pinnedShortcut =
+                        shortcutKeyToPinnedShortcuts[key] ?: retryDeepShortcutById(key)
                     if (pinnedShortcut == null) {
                         // The shortcut is no longer valid.
                         c.markDeleted(
@@ -376,6 +380,25 @@
     }
 
     /**
+     * It is possible that the data was cleared from ShortcutManager after it was restored. In that
+     * instance, the Launcher would have a valid Shortcut id, but ShortcutManager wouldn't recognize
+     * it as valid. Here we retry by querying ShortcutManager by package name and shortcut id.
+     */
+    private fun retryDeepShortcutById(key: ShortcutKey): ShortcutInfo? {
+        FileLog.d(TAG, "retryDeepShortcutById: package=${key.packageName}, shortcutId=${key.id}")
+        return launcherApps
+            .getShortcuts(
+                ShortcutQuery().apply {
+                    setPackage(key.packageName)
+                    setShortcutIds(listOf(key.id))
+                    setQueryFlags(ShortcutRequest.ALL)
+                },
+                key.user,
+            )
+            ?.firstOrNull()
+    }
+
+    /**
      * Loads CollectionInfo information from the database and formats it. This function runs while
      * LoaderTask is still active; some of the processing for folder content items is done after all
      * the items in the workspace have been loaded. The loaded and formatted CollectionInfo is then
@@ -521,7 +544,7 @@
                         appWidgetInfo.providerName,
                         appWidgetInfo.user,
                     )
-                iconCache.getTitleAndIconForApp(appWidgetInfo.pendingItemInfo, false)
+                iconCache.getTitleAndIconForApp(appWidgetInfo.pendingItemInfo, DEFAULT_LOOKUP_FLAG)
             }
             WidgetInflater.TYPE_REAL ->
                 WidgetSizes.updateWidgetSizeRangesAsync(
diff --git a/src/com/android/launcher3/model/data/AppPairInfo.kt b/src/com/android/launcher3/model/data/AppPairInfo.kt
index 3496c17..073d0e0 100644
--- a/src/com/android/launcher3/model/data/AppPairInfo.kt
+++ b/src/com/android/launcher3/model/data/AppPairInfo.kt
@@ -20,6 +20,7 @@
 import com.android.launcher3.LauncherSettings
 import com.android.launcher3.R
 import com.android.launcher3.icons.IconCache
+import com.android.launcher3.icons.cache.CacheLookupFlag.Companion.DEFAULT_LOOKUP_FLAG
 import com.android.launcher3.logger.LauncherAtom
 import com.android.launcher3.views.ActivityContext
 
@@ -32,9 +33,8 @@
     }
 
     /** Convenience constructor, calls primary constructor and init block */
-    constructor(app1: WorkspaceItemInfo, app2: WorkspaceItemInfo) : this() {
-        add(app1)
-        add(app2)
+    constructor(apps: List<WorkspaceItemInfo>) : this() {
+        apps.forEach(this::add)
     }
 
     /** Creates a new AppPairInfo that is a copy of the provided one. */
@@ -73,16 +73,17 @@
         val isTablet =
             (ActivityContext.lookupContext(context) as ActivityContext).getDeviceProfile().isTablet
         return Pair(
-            isTablet || !getFirstApp().isNonResizeable(),
-            isTablet || !getSecondApp().isNonResizeable(),
+            isTablet || !getFirstApp().isNonResizeable,
+            isTablet || !getSecondApp().isNonResizeable,
         )
     }
 
     /** Fetches high-res icons for member apps if needed. */
     fun fetchHiResIconsIfNeeded(iconCache: IconCache) {
-        getAppContents().stream().filter(ItemInfoWithIcon::usingLowResIcon).forEach { member ->
-            iconCache.getTitleAndIcon(member, false)
-        }
+        getAppContents()
+            .stream()
+            .filter { it.matchingLookupFlag.useLowRes() }
+            .forEach { member -> iconCache.getTitleAndIcon(member, DEFAULT_LOOKUP_FLAG) }
     }
 
     /**
diff --git a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
index 6ac44ff..772ea7f 100644
--- a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
+++ b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
@@ -23,10 +23,10 @@
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.Flags;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.icons.BitmapInfo;
 import com.android.launcher3.icons.BitmapInfo.DrawableCreationFlags;
 import com.android.launcher3.icons.FastBitmapDrawable;
+import com.android.launcher3.icons.cache.CacheLookupFlag;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.pm.PackageInstallInfo;
 import com.android.launcher3.util.ApiWrapper;
@@ -185,10 +185,10 @@
     }
 
     /**
-     * Indicates whether we're using a low res icon
+     * Returns the lookup flag to match this current state of this info
      */
-    public boolean usingLowResIcon() {
-        return bitmap.isLowRes();
+    public CacheLookupFlag getMatchingLookupFlag() {
+        return bitmap.getMatchingLookupFlag();
     }
 
     /**
diff --git a/src/com/android/launcher3/model/data/WorkspaceItemInfo.java b/src/com/android/launcher3/model/data/WorkspaceItemInfo.java
index 9af61f0..0a5dd62 100644
--- a/src/com/android/launcher3/model/data/WorkspaceItemInfo.java
+++ b/src/com/android/launcher3/model/data/WorkspaceItemInfo.java
@@ -142,7 +142,7 @@
                 .put(Favorites.OPTIONS, options)
                 .put(Favorites.RESTORED, status);
 
-        if (!usingLowResIcon()) {
+        if (!getMatchingLookupFlag().useLowRes()) {
             writer.putIcon(bitmap, user);
         }
     }
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index 63c9d94..329d9df 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -210,6 +210,30 @@
         }
     }
 
+    public static final Factory<ActivityContext> PIN_UNPIN_ITEM =
+            (context, itemInfo, originalView) -> {
+                // Predicted items use {@code HotseatPredictionController.PinPrediction} shortcut
+                // to pin.
+                if (itemInfo.isPredictedItem()) {
+                    return null;
+                }
+                return new PinUnpinItem<>(context, itemInfo, originalView);
+            };
+
+    private static class PinUnpinItem<T extends ActivityContext> extends SystemShortcut<T> {
+        PinUnpinItem(T target, ItemInfo itemInfo, @NonNull View originalView) {
+            // TODO(b/375648361): Check the pin state of the item to determine if the pin or the
+            //  unpin option should be used.
+            super(R.drawable.ic_pin, R.string.pin_to_taskbar, target,
+                    itemInfo, originalView);
+        }
+
+        @Override
+        public void onClick(View view) {
+            // TODO(b/375648361): Pin/Unpin the item here.
+        }
+    }
+
     public static final Factory<ActivityContext> PRIVATE_PROFILE_INSTALL =
             (context, itemInfo, originalView) -> {
                 if (originalView == null) {
diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index 8db981f..f56888b 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -124,11 +124,13 @@
         LauncherPrefs.get(context).removeSync(RESTORE_DEVICE);
 
         DeviceGridState deviceGridState = new DeviceGridState(context);
+        FileLog.d(TAG, "restoreIfNeeded: deviceGridState from context: " + deviceGridState);
         String oldPhoneFileName = deviceGridState.getDbFile();
         List<String> previousDbs = existingDbs(context);
         removeOldDBs(context, oldPhoneFileName);
         // The idp before this contains data about the old phone, after this it becomes the idp
         // of the current phone.
+        FileLog.d(TAG, "Resetting IDP to default for restore dest device");
         idp.reset(context);
         trySettingPreviousGridAsCurrent(context, idp, oldPhoneFileName, previousDbs);
     }
@@ -144,17 +146,24 @@
                 context, oldPhoneDbFileName);
         // The grid option could be null if current phone doesn't support the previous db.
         if (oldPhoneGridOption != null) {
+            FileLog.d(TAG, "trySettingPreviousGridAsCurrent:"
+                    + ", oldPhoneDbFileName: " + oldPhoneDbFileName
+                    + ", oldPhoneGridOption: " + oldPhoneGridOption
+                    + ", previousDbs: " + previousDbs);
 
             /* If the user only used the default db on the previous phone and the new default db is
              * bigger than or equal to the previous one, then keep the new default db */
             if (previousDbs.size() == 1 && oldPhoneGridOption.numColumns <= idp.numColumns
                     && oldPhoneGridOption.numRows <= idp.numRows) {
                 /* Keep the user in default grid */
+                FileLog.d(TAG, "Keeping default db from restore as current grid");
                 return;
             }
             /*
              * Here we are setting the previous db as the current one.
              */
+            FileLog.d(TAG, "Setting grid from old device as current grid: "
+                + "oldPhoneGridOption:" + oldPhoneGridOption.name);
             idp.setCurrentGrid(context, oldPhoneGridOption.name);
         }
     }
diff --git a/src/com/android/launcher3/statemanager/StatefulActivity.java b/src/com/android/launcher3/statemanager/StatefulActivity.java
index 079191f..f21e5da 100644
--- a/src/com/android/launcher3/statemanager/StatefulActivity.java
+++ b/src/com/android/launcher3/statemanager/StatefulActivity.java
@@ -18,6 +18,7 @@
 import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
 import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
 
+import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE_RECREATE_TO_UPDATE_THEME;
 import static com.android.launcher3.LauncherState.FLAG_NON_INTERACTIVE;
 
 import android.content.Context;
@@ -29,6 +30,7 @@
 import android.view.View;
 
 import androidx.annotation.CallSuper;
+import androidx.annotation.NonNull;
 
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.LauncherRootView;
@@ -54,6 +56,7 @@
 
     protected Configuration mOldConfig;
     private int mOldRotation;
+    private boolean mRecreateToUpdateTheme = false;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -63,6 +66,18 @@
         mOldRotation = WindowManagerProxy.INSTANCE.get(this).getRotation(this);
     }
 
+    @Override
+    protected void onSaveInstanceState(@NonNull Bundle outState) {
+        outState.putBoolean(RUNTIME_STATE_RECREATE_TO_UPDATE_THEME, mRecreateToUpdateTheme);
+        super.onSaveInstanceState(outState);
+    }
+
+    @Override
+    protected void recreateToUpdateTheme() {
+        mRecreateToUpdateTheme = true;
+        super.recreateToUpdateTheme();
+    }
+
     /**
      * Create handlers to control the property changes for this activity
      */
diff --git a/src/com/android/launcher3/states/RotationHelper.java b/src/com/android/launcher3/states/RotationHelper.java
index 7d7ccd3..9376518 100644
--- a/src/com/android/launcher3/states/RotationHelper.java
+++ b/src/com/android/launcher3/states/RotationHelper.java
@@ -29,6 +29,7 @@
 import android.content.Context;
 import android.os.Handler;
 import android.os.Message;
+import android.util.Log;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.WorkerThread;
@@ -210,7 +211,9 @@
         }
 
         final int activityFlags;
-        if (mStateHandlerRequest != REQUEST_NONE) {
+        if (mIsFixedLandscape) {
+            activityFlags = SCREEN_ORIENTATION_USER_LANDSCAPE;
+        } else if (mStateHandlerRequest != REQUEST_NONE) {
             activityFlags = mStateHandlerRequest == REQUEST_LOCK ?
                     SCREEN_ORIENTATION_LOCKED : SCREEN_ORIENTATION_UNSPECIFIED;
         } else if (mCurrentTransitionRequest != REQUEST_NONE) {
@@ -218,8 +221,6 @@
                     SCREEN_ORIENTATION_LOCKED : SCREEN_ORIENTATION_UNSPECIFIED;
         } else if (mCurrentStateRequest == REQUEST_LOCK) {
             activityFlags = SCREEN_ORIENTATION_LOCKED;
-        } else if (mIsFixedLandscape) {
-            activityFlags = SCREEN_ORIENTATION_USER_LANDSCAPE;
         } else if (mIgnoreAutoRotateSettings || mCurrentStateRequest == REQUEST_ROTATE
                 || mHomeRotationEnabled || mForceAllowRotationForTesting) {
             activityFlags = SCREEN_ORIENTATION_UNSPECIFIED;
@@ -230,6 +231,7 @@
         }
         if (activityFlags != mLastActivityFlags) {
             mLastActivityFlags = activityFlags;
+            Log.d("b/380940677", toString());
             mRequestOrientationHandler.sendEmptyMessage(activityFlags);
         }
     }
@@ -257,9 +259,9 @@
         return String.format("[mStateHandlerRequest=%d, mCurrentStateRequest=%d, "
                         + "mLastActivityFlags=%d, mIgnoreAutoRotateSettings=%b, "
                         + "mHomeRotationEnabled=%b, mForceAllowRotationForTesting=%b,"
-                        + " mDestroyed=%b]",
+                        + " mDestroyed=%b, mIsFixedLandscape=%b]",
                 mStateHandlerRequest, mCurrentStateRequest, mLastActivityFlags,
                 mIgnoreAutoRotateSettings, mHomeRotationEnabled, mForceAllowRotationForTesting,
-                mDestroyed);
+                mDestroyed, mIsFixedLandscape);
     }
 }
diff --git a/src/com/android/launcher3/util/window/WindowManagerProxy.java b/src/com/android/launcher3/util/window/WindowManagerProxy.java
index 84b4a36..1d9751e 100644
--- a/src/com/android/launcher3/util/window/WindowManagerProxy.java
+++ b/src/com/android/launcher3/util/window/WindowManagerProxy.java
@@ -27,7 +27,6 @@
 import static com.android.launcher3.testing.shared.ResourceUtils.STATUS_BAR_HEIGHT;
 import static com.android.launcher3.testing.shared.ResourceUtils.STATUS_BAR_HEIGHT_LANDSCAPE;
 import static com.android.launcher3.testing.shared.ResourceUtils.STATUS_BAR_HEIGHT_PORTRAIT;
-import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
 import static com.android.launcher3.util.RotationUtils.deltaRotation;
 import static com.android.launcher3.util.RotationUtils.rotateRect;
 import static com.android.launcher3.util.RotationUtils.rotateSize;
@@ -52,37 +51,33 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.launcher3.R;
+import com.android.launcher3.dagger.LauncherAppSingleton;
+import com.android.launcher3.dagger.LauncherBaseAppComponent;
 import com.android.launcher3.testing.shared.ResourceUtils;
-import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.DaggerSingletonObject;
 import com.android.launcher3.util.NavigationMode;
-import com.android.launcher3.util.ResourceBasedOverride;
-import com.android.launcher3.util.SafeCloseable;
 import com.android.launcher3.util.WindowBounds;
 
 import java.util.ArrayList;
 import java.util.List;
 
+import javax.inject.Inject;
+
 /**
  * Utility class for mocking some window manager behaviours
  */
-public class WindowManagerProxy implements ResourceBasedOverride, SafeCloseable {
+@LauncherAppSingleton
+public class WindowManagerProxy {
 
     private static final String TAG = "WindowManagerProxy";
     public static final int MIN_TABLET_WIDTH = 600;
 
-    public static final MainThreadInitializedObject<WindowManagerProxy> INSTANCE =
-            forOverride(WindowManagerProxy.class, R.string.window_manager_proxy_class);
+    public static final DaggerSingletonObject<WindowManagerProxy> INSTANCE =
+            new DaggerSingletonObject<>(LauncherBaseAppComponent::getWmProxy);
 
     protected final boolean mTaskbarDrawnInProcess;
 
-    /**
-     * Creates a new instance of proxy, applying any overrides
-     */
-    public static WindowManagerProxy newInstance(Context context) {
-        return Overrides.getObject(WindowManagerProxy.class, context,
-                R.string.window_manager_proxy_class);
-    }
-
+    @Inject
     public WindowManagerProxy() {
         this(false);
     }
@@ -483,9 +478,6 @@
         return NavigationMode.NO_BUTTON;
     }
 
-    @Override
-    public void close() { }
-
     /**
      * @see DisplayCutout#getSafeInsets
      */
diff --git a/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java b/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java
index e100157..d4538dd 100644
--- a/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java
+++ b/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java
@@ -15,12 +15,15 @@
  */
 package com.android.launcher3.widget;
 
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN;
 
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.widget.LauncherAppWidgetProviderInfo.fromProviderInfo;
+
+import android.appwidget.AppWidgetProviderInfo;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
-import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.PorterDuff;
@@ -30,17 +33,19 @@
 import android.os.Handler;
 import android.util.Log;
 import android.util.Size;
+import android.widget.RemoteViews;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
+import androidx.core.os.BuildCompat;
 
 import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Flags;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.icons.BitmapRenderer;
 import com.android.launcher3.icons.LauncherIcons;
-import com.android.launcher3.icons.ShadowGenerator;
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.pm.ShortcutConfigActivityInfo;
 import com.android.launcher3.util.CancellableTask;
@@ -52,20 +57,19 @@
 import java.util.concurrent.ExecutionException;
 import java.util.function.Consumer;
 
-/** Utility class to load widget previews */
+/**
+ * Utility class to generate widget previews
+ *
+ * Note that it no longer uses database, all previews are freshly generated
+ */
 public class DatabaseWidgetPreviewLoader {
 
     private static final String TAG = "WidgetPreviewLoader";
 
     private final Context mContext;
-    private final float mPreviewBoxCornerRadius;
 
     public DatabaseWidgetPreviewLoader(Context context) {
         mContext = context;
-        float previewCornerRadius = RoundedCornerEnforcement.computeEnforcedRadius(context);
-        mPreviewBoxCornerRadius = previewCornerRadius > 0
-                ? previewCornerRadius
-                : mContext.getResources().getDimension(R.dimen.widget_preview_corner_radius);
     }
 
     /**
@@ -77,10 +81,10 @@
     public CancellableTask loadPreview(
             @NonNull WidgetItem item,
             @NonNull Size previewSize,
-            @NonNull Consumer<Bitmap> callback) {
+            @NonNull Consumer<WidgetPreviewInfo> callback) {
         Handler handler = getLoaderExecutor().getHandler();
-        CancellableTask<Bitmap> request = new CancellableTask<>(
-                () -> generatePreview(item, previewSize.getWidth(), previewSize.getHeight()),
+        CancellableTask<WidgetPreviewInfo> request = new CancellableTask<>(
+                () -> generatePreviewInfoBg(item, previewSize.getWidth(), previewSize.getHeight()),
                 MAIN_EXECUTOR,
                 callback);
         Utilities.postAsyncCallback(handler, request);
@@ -93,6 +97,39 @@
         return Executors.UI_HELPER_EXECUTOR;
     }
 
+    /** Generated the preview object. This method must be called on a background thread */
+    @VisibleForTesting
+    @NonNull
+    public WidgetPreviewInfo generatePreviewInfoBg(
+            WidgetItem item, int previewWidth, int previewHeight) {
+        WidgetPreviewInfo result = new WidgetPreviewInfo();
+
+        AppWidgetProviderInfo widgetInfo = item.widgetInfo;
+        if (BuildCompat.isAtLeastV() && Flags.enableGeneratedPreviews() && widgetInfo != null
+                && ((widgetInfo.generatedPreviewCategories & WIDGET_CATEGORY_HOME_SCREEN) != 0)) {
+            result.remoteViews = new WidgetManagerHelper(mContext)
+                    .loadGeneratedPreview(widgetInfo, WIDGET_CATEGORY_HOME_SCREEN);
+            if (result.remoteViews != null) {
+                result.providerInfo = widgetInfo;
+            }
+        }
+
+        if (result.providerInfo == null && widgetInfo != null
+                && widgetInfo.previewLayout != Resources.ID_NULL) {
+            result.providerInfo = fromProviderInfo(mContext, widgetInfo.clone());
+            // A hack to force the initial layout to be the preview layout since there is no API for
+            // rendering a preview layout for work profile apps yet. For non-work profile layout, a
+            // proper solution is to use RemoteViews(PackageName, LayoutId).
+            result.providerInfo.initialLayout = item.widgetInfo.previewLayout;
+        }
+
+        if (result.providerInfo == null) {
+            // fallback to bitmap preview
+            result.previewBitmap = generatePreview(item, previewWidth, previewHeight);
+        }
+        return result;
+    }
+
     /**
      * Returns a generated preview for a widget and if the preview should be saved in persistent
      * storage.
@@ -232,21 +269,6 @@
         });
     }
 
-    private RectF drawBoxWithShadow(Canvas c, int width, int height) {
-        Resources res = mContext.getResources();
-
-        ShadowGenerator.Builder builder = new ShadowGenerator.Builder(Color.WHITE);
-        builder.shadowBlur = res.getDimension(R.dimen.widget_preview_shadow_blur);
-        builder.radius = mPreviewBoxCornerRadius;
-        builder.keyShadowDistance = res.getDimension(R.dimen.widget_preview_key_shadow_distance);
-
-        builder.bounds.set(builder.shadowBlur, builder.shadowBlur,
-                width - builder.shadowBlur,
-                height - builder.shadowBlur - builder.keyShadowDistance);
-        builder.drawShadow(c);
-        return builder.bounds;
-    }
-
     private Bitmap generateShortcutPreview(
             ShortcutConfigActivityInfo info, int maxWidth, int maxHeight) {
         int iconSize = ActivityContext.lookupContext(mContext).getDeviceProfile().allAppsIconSizePx;
@@ -280,4 +302,15 @@
             throw new RuntimeException(e);
         }
     }
+
+    /**
+     * Simple class to hold preview information
+     */
+    public static class WidgetPreviewInfo {
+
+        public AppWidgetProviderInfo providerInfo;
+        public RemoteViews remoteViews;
+
+        public Bitmap previewBitmap;
+    }
 }
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
index 0cf1f2e..b07d807 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
@@ -123,7 +123,7 @@
     @Override
     public void setAppWidget(int appWidgetId, AppWidgetProviderInfo info) {
         super.setAppWidget(appWidgetId, info);
-        if (!mTrackingWidgetUpdate) {
+        if (!mTrackingWidgetUpdate && appWidgetId != -1) {
             mTrackingWidgetUpdate = true;
             Trace.beginAsyncSection(TRACE_METHOD_NAME + info.provider, appWidgetId);
             Log.i(TAG, "App widget created with id: " + appWidgetId);
@@ -255,16 +255,6 @@
     }
 
     @Override
-    public AppWidgetProviderInfo getAppWidgetInfo() {
-        AppWidgetProviderInfo info = super.getAppWidgetInfo();
-        if (info != null && !(info instanceof LauncherAppWidgetProviderInfo)) {
-            throw new IllegalStateException("Launcher widget must have"
-                    + " LauncherAppWidgetProviderInfo");
-        }
-        return info;
-    }
-
-    @Override
     public void getFocusedRect(Rect r) {
         super.getFocusedRect(r);
         // Outset to a larger rect for drawing a padding between focus outline and widget
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index 9e635a3..4811a17 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -16,18 +16,17 @@
 
 package com.android.launcher3.widget;
 
-import static android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN;
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
 
 import static com.android.launcher3.Flags.enableWidgetTapToAdd;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_TRAY;
-import static com.android.launcher3.widget.LauncherAppWidgetProviderInfo.fromProviderInfo;
 import static com.android.launcher3.widget.util.WidgetSizes.getWidgetItemSizePx;
 
 import android.animation.Animator;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
+import android.appwidget.AppWidgetProviderInfo;
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Rect;
@@ -48,13 +47,10 @@
 import android.widget.RemoteViews;
 import android.widget.TextView;
 
-import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.app.animation.Interpolators;
 import com.android.launcher3.CheckLongPressHelper;
-import com.android.launcher3.Flags;
-import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.AnimatedPropertySetter;
@@ -65,11 +61,10 @@
 import com.android.launcher3.model.data.PackageItemInfo;
 import com.android.launcher3.util.CancellableTask;
 import com.android.launcher3.views.ActivityContext;
+import com.android.launcher3.widget.DatabaseWidgetPreviewLoader.WidgetPreviewInfo;
 import com.android.launcher3.widget.picker.util.WidgetPreviewContainerSize;
 import com.android.launcher3.widget.util.WidgetSizes;
 
-import java.util.function.Consumer;
-
 /**
  * Represents the individual cell of the widget inside the widget tray. The preview is drawn
  * horizontally centered, and scaled down if needed.
@@ -242,17 +237,6 @@
      * Applies the item to this view
      */
     public void applyFromCellItem(WidgetItem item) {
-        applyFromCellItem(item, this::applyPreview, /*cachedPreview=*/null);
-    }
-
-    /**
-     * Applies the item to this view
-     * @param item item to apply
-     * @param callback callback when preview is loaded in case the preview is being loaded or cached
-     * @param cachedPreview previously cached preview bitmap is present
-     */
-    public void applyFromCellItem(WidgetItem item, @NonNull Consumer<Bitmap> callback,
-            @Nullable Bitmap cachedPreview) {
         Context context = getContext();
         mItem = item;
         mWidgetSize = getWidgetItemSizePx(getContext(), mActivity.getDeviceProfile(), mItem);
@@ -283,37 +267,28 @@
         }
 
         if (mRemoteViewsPreview != null) {
-            mAppWidgetHostViewPreview = createAppWidgetHostView(context);
-            setAppWidgetHostViewPreview(mAppWidgetHostViewPreview, item.widgetInfo,
-                    mRemoteViewsPreview);
-        } else if (Flags.enableGeneratedPreviews()
-                && item.hasGeneratedPreview(WIDGET_CATEGORY_HOME_SCREEN)) {
-            mAppWidgetHostViewPreview = createAppWidgetHostView(context);
-            setAppWidgetHostViewPreview(mAppWidgetHostViewPreview, item.widgetInfo,
-                    item.generatedPreviews.get(WIDGET_CATEGORY_HOME_SCREEN));
-        } else if (item.hasPreviewLayout()) {
-            // If the context is a Launcher activity, DragView will show mAppWidgetHostViewPreview
-            // as a preview during drag & drop. And thus, we should use LauncherAppWidgetHostView,
-            // which supports applying local color extraction during drag & drop.
-            mAppWidgetHostViewPreview = isLauncherContext(context)
-                    ? new LauncherAppWidgetHostView(context)
-                    : createAppWidgetHostView(context);
-            LauncherAppWidgetProviderInfo providerInfo =
-                    fromProviderInfo(context, item.widgetInfo.clone());
-            // A hack to force the initial layout to be the preview layout since there is no API for
-            // rendering a preview layout for work profile apps yet. For non-work profile layout, a
-            // proper solution is to use RemoteViews(PackageName, LayoutId).
-            providerInfo.initialLayout = item.widgetInfo.previewLayout;
-            setAppWidgetHostViewPreview(mAppWidgetHostViewPreview, providerInfo, null);
-        } else if (cachedPreview != null) {
-            applyPreview(cachedPreview);
+            WidgetPreviewInfo previewInfo = new WidgetPreviewInfo();
+            previewInfo.providerInfo = item.widgetInfo;
+            previewInfo.remoteViews = mRemoteViewsPreview;
+            applyPreview(previewInfo);
         } else {
             if (mActiveRequest == null) {
-                mActiveRequest = mWidgetPreviewLoader.loadPreview(mItem, mWidgetSize, callback);
+                mActiveRequest = mWidgetPreviewLoader.loadPreview(
+                        mItem, mWidgetSize, this::applyPreview);
             }
         }
     }
 
+    private void applyPreview(WidgetPreviewInfo previewInfo) {
+        if (previewInfo.providerInfo != null) {
+            mAppWidgetHostViewPreview = createAppWidgetHostView(getContext());
+            setAppWidgetHostViewPreview(mAppWidgetHostViewPreview, previewInfo.providerInfo,
+                    previewInfo.remoteViews);
+        } else {
+            applyBitmapPreview(previewInfo.previewBitmap);
+        }
+    }
+
     private void initPreviewContainerSizeAndScale() {
         WidgetPreviewContainerSize previewSize = WidgetPreviewContainerSize.Companion.forItem(mItem,
                 mActivity.getDeviceProfile());
@@ -337,7 +312,7 @@
 
     private void setAppWidgetHostViewPreview(
             NavigableAppWidgetHostView appWidgetHostViewPreview,
-            LauncherAppWidgetProviderInfo providerInfo,
+            AppWidgetProviderInfo providerInfo,
             @Nullable RemoteViews remoteViews) {
         appWidgetHostViewPreview.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
         appWidgetHostViewPreview.setAppWidget(/* appWidgetId= */ -1, providerInfo);
@@ -349,7 +324,7 @@
                 mWidgetSize.getWidth(), mWidgetSize.getHeight(), Gravity.CENTER);
         mWidgetImageContainer.addView(appWidgetHostViewPreview, /* index= */ 0, widgetHostLP);
         mWidgetImage.setVisibility(View.GONE);
-        applyPreview(null);
+        applyBitmapPreview(null);
 
         appWidgetHostViewPreview.addOnLayoutChangeListener(
                 (v, l, t, r, b, ol, ot, or, ob) ->
@@ -407,7 +382,7 @@
         mAnimatePreview = shouldAnimate;
     }
 
-    private void applyPreview(Bitmap bitmap) {
+    private void applyBitmapPreview(Bitmap bitmap) {
         if (bitmap != null) {
             Drawable drawable = new RoundDrawableWrapper(
                     new FastBitmapDrawable(bitmap), mEnforcedCornerRadius);
@@ -496,8 +471,8 @@
         mLongPressHelper.cancelLongPress();
     }
 
-    private static NavigableAppWidgetHostView createAppWidgetHostView(Context context) {
-        return new NavigableAppWidgetHostView(context) {
+    private static LauncherAppWidgetHostView createAppWidgetHostView(Context context) {
+        return new LauncherAppWidgetHostView(context) {
             @Override
             protected boolean shouldAllowDirectClick() {
                 return false;
@@ -505,10 +480,6 @@
         };
     }
 
-    private static boolean isLauncherContext(Context context) {
-        return ActivityContext.lookupContext(context) instanceof Launcher;
-    }
-
     @Override
     public CharSequence getAccessibilityClassName() {
         return WidgetCell.class.getName();
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index 150806a..d850fc6 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -24,6 +24,7 @@
 import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
 import static com.android.launcher3.views.RecyclerViewFastScroller.FastScrollerLocation.WIDGET_SCROLLER;
 
+import static java.lang.Math.abs;
 import static java.util.Collections.emptyList;
 
 import android.animation.Animator;
@@ -41,6 +42,7 @@
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewConfiguration;
 import android.view.ViewGroup;
 import android.view.ViewParent;
 import android.view.WindowInsets;
@@ -119,6 +121,10 @@
     protected int mRecommendationsCurrentPage = 0;
     protected final SparseArray<AdapterHolder> mAdapters = new SparseArray();
 
+    // Helps with removing focus from searchbar by analyzing motion events.
+    private final SearchClearFocusHelper mSearchClearFocusHelper = new SearchClearFocusHelper();
+    private final float mTouchSlop; // initialized in constructor
+
     private final OnAttachStateChangeListener mBindScrollbarInSearchMode =
             new OnAttachStateChangeListener() {
                 @Override
@@ -165,6 +171,7 @@
 
     public WidgetsFullSheet(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
+        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
         mDeviceProfile = mActivityContext.getDeviceProfile();
         mUserCache = UserCache.INSTANCE.get(context);
         mHasWorkProfile = mUserCache.getUserProfiles()
@@ -714,10 +721,14 @@
     public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
             mNoIntercept = shouldScroll(ev);
-            if (mSearchBar.isSearchBarFocused()
-                    && !getPopupContainer().isEventOverView(mSearchBarContainer, ev)) {
-                mSearchBar.clearSearchBarFocus();
-            }
+        }
+
+        // Clear focus only if user touched outside of search area and handling focus out ourselves
+        // was necessary (e.g. when it's not predictive back, but other user interaction).
+        if (mSearchBar.isSearchBarFocused()
+                && !getPopupContainer().isEventOverView(mSearchBarContainer, ev)
+                && mSearchClearFocusHelper.shouldClearFocus(ev, mTouchSlop)) {
+            mSearchBar.clearSearchBarFocus();
         }
 
         return super.onControllerInterceptTouchEvent(ev);
@@ -1141,4 +1152,53 @@
             mWidgetsListAdapter.setMaxHorizontalSpansPxPerRow(mMaxSpanPerRow);
         }
     }
+
+    /**
+     * Helper to identify if searchbar's focus can be cleared when user performs an action
+     * outside search.
+     */
+    private static class SearchClearFocusHelper {
+        private float mFirstInteractionX = -1f;
+        private float mFirstInteractionY = -1f;
+
+        /**
+         * For a given [MotionEvent] indicates if we should clear focus from search (and hide IME).
+         */
+        boolean shouldClearFocus(MotionEvent ev, float touchSlop) {
+            int action = ev.getAction();
+            boolean clearFocus = false;
+
+            if (action == MotionEvent.ACTION_DOWN) {
+                mFirstInteractionX = ev.getX();
+                mFirstInteractionY = ev.getY();
+            } else if (action == MotionEvent.ACTION_CANCEL) {
+                // This is when user performed a gesture e.g. predictive back
+                // We don't handle it ourselves and let IME handle the close.
+                mFirstInteractionY = -1;
+                mFirstInteractionX = -1;
+            } else if (action == MotionEvent.ACTION_UP) {
+                // Its clear that user action wasn't predictive back - but press / scroll etc. that
+                // should hide the keyboard.
+                clearFocus = true;
+                mFirstInteractionY = -1;
+                mFirstInteractionX = -1;
+            } else if (action == MotionEvent.ACTION_MOVE) {
+                // Sometimes, on move, we may not receive ACTION_UP, but if the move was within
+                // touch slop and we didn't know if its moved or cancelled, we can clear focus.
+                // Example case: Apps list is small and you do a little scroll on list - in such, we
+                // want to still hide the keyboard.
+                if (mFirstInteractionX != -1 && mFirstInteractionY != -1) {
+                    float distY = abs(mFirstInteractionY - ev.getY());
+                    float distX = abs(mFirstInteractionX - ev.getX());
+                    if (distY >= touchSlop || distX >= touchSlop) {
+                        clearFocus = true;
+                        mFirstInteractionY = -1;
+                        mFirstInteractionX = -1;
+                    }
+                }
+            }
+
+            return clearFocus;
+        }
+    }
 }
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListHeader.java b/src/com/android/launcher3/widget/picker/WidgetsListHeader.java
index d164dd0..2f123a3 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListHeader.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListHeader.java
@@ -234,12 +234,9 @@
             mIconLoadRequest.cancel();
             mIconLoadRequest = null;
         }
-        if (getTag() instanceof ItemInfoWithIcon) {
-            ItemInfoWithIcon info = (ItemInfoWithIcon) getTag();
-            if (info.usingLowResIcon()) {
-                mIconLoadRequest = LauncherAppState.getInstance(getContext()).getIconCache()
-                        .updateIconInBackground(this, info);
-            }
+        if (getTag() instanceof ItemInfoWithIcon info && info.getMatchingLookupFlag().useLowRes()) {
+            mIconLoadRequest = LauncherAppState.getInstance(getContext()).getIconCache()
+                    .updateIconInBackground(this, info);
         }
     }
 }
diff --git a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
index f9bd5f1..df76400 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
@@ -22,6 +22,7 @@
 import static com.android.launcher3.UtilitiesKt.CLIP_TO_PADDING_FALSE_MODIFIER;
 import static com.android.launcher3.UtilitiesKt.modifyAttributesOnViewTree;
 import static com.android.launcher3.UtilitiesKt.restoreAttributesOnViewTree;
+import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG;
 import static com.android.launcher3.widget.picker.WidgetsListItemAnimator.WIDGET_LIST_ITEM_APPEARANCE_DELAY;
 import static com.android.launcher3.widget.picker.model.data.WidgetPickerDataUtils.findContentEntryForPackageUser;
 
@@ -51,6 +52,7 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.icons.cache.CacheLookupFlag;
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.model.data.PackageItemInfo;
 import com.android.launcher3.recyclerview.ViewHolderBinder;
@@ -691,8 +693,8 @@
         }
 
         @Override
-        public boolean usingLowResIcon() {
-            return false;
+        public CacheLookupFlag getMatchingLookupFlag() {
+            return DEFAULT_LOOKUP_FLAG;
         }
     }
 }
diff --git a/src_no_quickstep/com/android/launcher3/dagger/LauncherAppComponent.java b/src_no_quickstep/com/android/launcher3/dagger/LauncherAppComponent.java
index 63d87e8..f4f5a08 100644
--- a/src_no_quickstep/com/android/launcher3/dagger/LauncherAppComponent.java
+++ b/src_no_quickstep/com/android/launcher3/dagger/LauncherAppComponent.java
@@ -22,7 +22,7 @@
  * Root component for Dagger injection for Launcher AOSP.
  */
 @LauncherAppSingleton
-@Component
+@Component(modules = LauncherAppModule.class)
 public interface LauncherAppComponent extends LauncherBaseAppComponent {
     /** Builder for aosp LauncherAppComponent. */
     @Component.Builder
diff --git a/src_no_quickstep/com/android/launcher3/dagger/LauncherAppModule.java b/src_no_quickstep/com/android/launcher3/dagger/LauncherAppModule.java
new file mode 100644
index 0000000..f7b8489
--- /dev/null
+++ b/src_no_quickstep/com/android/launcher3/dagger/LauncherAppModule.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.dagger;
+
+import dagger.Module;
+
+@Module
+public class LauncherAppModule { }
diff --git a/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt b/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
index c4519eb..8598917 100644
--- a/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
@@ -26,8 +26,11 @@
 import android.platform.test.rule.LimitDevicesRule
 import android.util.DisplayMetrics
 import android.view.Surface
-import androidx.test.core.app.ApplicationProvider
+import androidx.test.core.app.ApplicationProvider.getApplicationContext
 import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.dagger.LauncherAppComponent
+import com.android.launcher3.dagger.LauncherAppModule
+import com.android.launcher3.dagger.LauncherAppSingleton
 import com.android.launcher3.testing.shared.ResourceUtils
 import com.android.launcher3.util.DisplayController
 import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext
@@ -38,6 +41,8 @@
 import com.android.launcher3.util.window.CachedDisplayInfo
 import com.android.launcher3.util.window.WindowManagerProxy
 import com.google.common.truth.Truth
+import dagger.BindsInstance
+import dagger.Component
 import java.io.BufferedReader
 import java.io.File
 import java.io.PrintWriter
@@ -62,9 +67,9 @@
 abstract class AbstractDeviceProfileTest {
     protected val testContext: Context = InstrumentationRegistry.getInstrumentation().context
     protected lateinit var context: SandboxContext
-    protected open val runningContext: Context = ApplicationProvider.getApplicationContext()
+    protected open val runningContext: Context = getApplicationContext()
     private val displayController: DisplayController = mock()
-    private val windowManagerProxy: WindowManagerProxy = mock()
+    private val windowManagerProxy: MyWmProxy = mock()
     private val launcherPrefs: LauncherPrefs = mock()
 
     @get:Rule val setFlagsRule = SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT)
@@ -290,9 +295,9 @@
             .thenReturn(
                 if (isGestureMode) NavigationMode.NO_BUTTON else NavigationMode.THREE_BUTTONS
             )
-        doReturn(WindowManagerProxy.INSTANCE[runningContext].isTaskbarDrawnInProcess)
+        doReturn(WindowManagerProxy.INSTANCE[getApplicationContext()].isTaskbarDrawnInProcess)
             .whenever(windowManagerProxy)
-            .isTaskbarDrawnInProcess()
+            .isTaskbarDrawnInProcess
 
         val density = densityDpi / DisplayMetrics.DENSITY_DEFAULT.toFloat()
         val config =
@@ -304,8 +309,10 @@
             }
         val configurationContext = runningContext.createConfigurationContext(config)
         context = SandboxContext(configurationContext)
+        context.initDaggerComponent(
+            DaggerAbsDPTestSandboxComponent.builder().bindWMProxy(windowManagerProxy)
+        )
         context.putObject(DisplayController.INSTANCE, displayController)
-        context.putObject(WindowManagerProxy.INSTANCE, windowManagerProxy)
         context.putObject(LauncherPrefs.INSTANCE, launcherPrefs)
 
         whenever(launcherPrefs.get(LauncherPrefs.TASKBAR_PINNING)).thenReturn(false)
@@ -355,3 +362,19 @@
         return context.resources.getIdentifier(this, "xml", context.packageName)
     }
 }
+
+class MyWmProxy : WindowManagerProxy()
+
+@LauncherAppSingleton
+@Component(modules = [LauncherAppModule::class])
+interface AbsDPTestSandboxComponent : LauncherAppComponent {
+
+    override fun getWmProxy(): MyWmProxy
+
+    @Component.Builder
+    interface Builder : LauncherAppComponent.Builder {
+        @BindsInstance fun bindWMProxy(proxy: MyWmProxy): Builder
+
+        override fun build(): AbsDPTestSandboxComponent
+    }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/AutoInstallsLayoutTest.kt b/tests/multivalentTests/src/com/android/launcher3/AutoInstallsLayoutTest.kt
index f73a9d3..fcbb94b 100644
--- a/tests/multivalentTests/src/com/android/launcher3/AutoInstallsLayoutTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/AutoInstallsLayoutTest.kt
@@ -42,6 +42,7 @@
 import com.android.launcher3.LauncherSettings.Favorites.SPANY
 import com.android.launcher3.LauncherSettings.Favorites._ID
 import com.android.launcher3.dagger.LauncherAppComponent
+import com.android.launcher3.dagger.LauncherAppModule
 import com.android.launcher3.dagger.LauncherAppSingleton
 import com.android.launcher3.model.data.AppInfo
 import com.android.launcher3.pm.UserCache
@@ -66,7 +67,7 @@
 import org.mockito.Mock
 import org.mockito.MockitoAnnotations
 import org.mockito.kotlin.doReturn
-import org.mockito.kotlin.spy
+import org.mockito.kotlin.mock
 import org.mockito.kotlin.whenever
 
 /** Tests for [AutoInstallsLayout] */
@@ -165,7 +166,9 @@
 
     @Test
     fun work_item_added_to_home() {
-        val apiWrapperMock = spy(ApiWrapper.INSTANCE[targetContext])
+        val original = ApiWrapper.INSTANCE[targetContext]
+        val apiWrapperMock =
+            mock<MyApiWrapper>(defaultAnswer = { it.method.invoke(original, *it.arguments) })
         targetContext.initDaggerComponent(
             DaggerAutoInstallsLayoutTestComponent.builder().bindApiWrapper(apiWrapperMock)
         )
@@ -221,12 +224,17 @@
     }
 }
 
+class MyApiWrapper : ApiWrapper(null) {}
+
 @LauncherAppSingleton
-@Component
+@Component(modules = [LauncherAppModule::class])
 interface AutoInstallsLayoutTestComponent : LauncherAppComponent {
+
+    override fun getApiWrapper(): MyApiWrapper
+
     @Component.Builder
     interface Builder : LauncherAppComponent.Builder {
-        @BindsInstance fun bindApiWrapper(wrapper: ApiWrapper): Builder
+        @BindsInstance fun bindApiWrapper(wrapper: MyApiWrapper): Builder
 
         override fun build(): AutoInstallsLayoutTestComponent
     }
diff --git a/tests/src/com/android/launcher3/celllayout/testgenerator/ValidGridMigrationTestCaseGenerator.kt b/tests/multivalentTests/src/com/android/launcher3/celllayout/testgenerator/ValidGridMigrationTestCaseGenerator.kt
similarity index 95%
rename from tests/src/com/android/launcher3/celllayout/testgenerator/ValidGridMigrationTestCaseGenerator.kt
rename to tests/multivalentTests/src/com/android/launcher3/celllayout/testgenerator/ValidGridMigrationTestCaseGenerator.kt
index a006fd7..ad80b2d 100644
--- a/tests/src/com/android/launcher3/celllayout/testgenerator/ValidGridMigrationTestCaseGenerator.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/celllayout/testgenerator/ValidGridMigrationTestCaseGenerator.kt
@@ -32,7 +32,7 @@
  */
 fun generateItemsForTest(
     boards: List<CellLayoutBoard>,
-    repeatAfterRange: Point
+    repeatAfterRange: Point,
 ): List<WorkspaceItem> {
     val id = AtomicInteger(0)
     val widgetId = AtomicInteger(LauncherAppWidgetInfo.CUSTOM_WIDGET_ID - 1)
@@ -56,7 +56,7 @@
                 appWidgetProvider = "Hotseat icons don't have a provider",
                 intent = getIntent(id.get()),
                 type = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION,
-                container = LauncherSettings.Favorites.CONTAINER_HOTSEAT
+                container = LauncherSettings.Favorites.CONTAINER_HOTSEAT,
             )
         }
     var widgetEntries =
@@ -75,7 +75,7 @@
                     appWidgetProvider = getProvider(id.get()),
                     intent = "Widgets don't have intent",
                     type = LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET,
-                    container = LauncherSettings.Favorites.CONTAINER_DESKTOP
+                    container = LauncherSettings.Favorites.CONTAINER_DESKTOP,
                 )
             }
     widgetEntries = widgetEntries.filter { it.appWidgetProvider.contains("Provider4") }
@@ -95,7 +95,7 @@
                     appWidgetProvider = "Icons don't have providers",
                     intent = getIntent(id.get()),
                     type = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION,
-                    container = LauncherSettings.Favorites.CONTAINER_DESKTOP
+                    container = LauncherSettings.Favorites.CONTAINER_DESKTOP,
                 )
             }
     return widgetEntries + hotseatEntries + iconEntries
@@ -106,7 +106,7 @@
     val destBoards: List<CellLayoutBoard>,
     val srcSize: Point,
     val targetSize: Point,
-    val seed: Long
+    val seed: Long,
 )
 
 class ValidGridMigrationTestCaseGenerator(private val generator: Random) :
@@ -122,7 +122,7 @@
         boardGenerator: RandomBoardGenerator,
         width: Int,
         height: Int,
-        boardCount: Int
+        boardCount: Int,
     ): List<CellLayoutBoard> {
         val boards = mutableListOf<CellLayoutBoard>()
         for (i in 0 until boardCount) {
@@ -130,7 +130,7 @@
                 boardGenerator.generateBoard(
                     width,
                     height,
-                    boardGenerator.getRandom(0, width * height)
+                    boardGenerator.getRandom(0, width * height),
                 )
             )
         }
@@ -145,7 +145,7 @@
         val targetSize =
             Point(
                 randomBoardGenerator.getRandom(3, MAX_BOARD_SIZE),
-                randomBoardGenerator.getRandom(3, MAX_BOARD_SIZE)
+                randomBoardGenerator.getRandom(3, MAX_BOARD_SIZE),
             )
         val destBoards =
             if (isDestEmpty) {
@@ -155,7 +155,7 @@
                     boardGenerator = randomBoardGenerator,
                     width = targetSize.x,
                     height = targetSize.y,
-                    boardCount = randomBoardGenerator.getRandom(3, MAX_BOARD_COUNT)
+                    boardCount = randomBoardGenerator.getRandom(3, MAX_BOARD_COUNT),
                 )
             }
         return GridMigrationUnitTestCase(
@@ -164,12 +164,12 @@
                     boardGenerator = randomBoardGenerator,
                     width = width,
                     height = height,
-                    boardCount = randomBoardGenerator.getRandom(3, MAX_BOARD_COUNT)
+                    boardCount = randomBoardGenerator.getRandom(3, MAX_BOARD_COUNT),
                 ),
             destBoards = destBoards,
             srcSize = Point(width, height),
             targetSize = targetSize,
-            seed = seed
+            seed = seed,
         )
     }
 }
diff --git a/tests/multivalentTests/src/com/android/launcher3/folder/FolderNameProviderTest.java b/tests/multivalentTests/src/com/android/launcher3/folder/FolderNameProviderTest.java
index 9c15309..b6fa81c 100644
--- a/tests/multivalentTests/src/com/android/launcher3/folder/FolderNameProviderTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/folder/FolderNameProviderTest.java
@@ -23,6 +23,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.os.Process;
 import android.os.UserHandle;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -49,16 +50,17 @@
     @Before
     public void setUp() {
         mContext = new ActivityContextWrapper(getApplicationContext());
+        int workUserId = Process.myUserHandle().hashCode() + 1;
         mItem1 = new WorkspaceItemInfo(new AppInfo(
                 new ComponentName("a.b.c", "a.b.c/a.b.c.d"),
                 "title1",
-                UserHandle.of(10),
+                UserHandle.of(workUserId),
                 new Intent().setComponent(new ComponentName("a.b.c", "a.b.c/a.b.c.d"))
         ));
         mItem2 = new WorkspaceItemInfo(new AppInfo(
                 new ComponentName("a.b.c", "a.b.c/a.b.c.d"),
                 "title2",
-                UserHandle.of(10),
+                UserHandle.of(workUserId),
                 new Intent().setComponent(new ComponentName("a.b.c", "a.b.c/a.b.c.d"))
         ));
     }
diff --git a/tests/multivalentTests/src/com/android/launcher3/folder/FolderTest.kt b/tests/multivalentTests/src/com/android/launcher3/folder/FolderTest.kt
index 4eb335e..bcf5f0d 100644
--- a/tests/multivalentTests/src/com/android/launcher3/folder/FolderTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/folder/FolderTest.kt
@@ -58,6 +58,7 @@
 import junit.framework.TestCase.assertNull
 import junit.framework.TestCase.assertTrue
 import org.junit.After
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mockito
@@ -74,10 +75,16 @@
 @RunWith(AndroidJUnit4::class)
 class FolderTest {
 
-    private val context: Context =
-        ActivityContextWrapper(ApplicationProvider.getApplicationContext())
-    private val workspaceBuilder = TestWorkspaceBuilder(context)
-    private val folder: Folder = spy(Folder(context, null))
+    private lateinit var context: Context
+    private lateinit var workspaceBuilder: TestWorkspaceBuilder
+    private lateinit var folder: Folder
+
+    @Before
+    fun setUp() {
+        context = ActivityContextWrapper(ApplicationProvider.getApplicationContext())
+        workspaceBuilder = TestWorkspaceBuilder(context)
+        folder = spy(Folder(context, null))
+    }
 
     @After
     fun tearDown() {
@@ -412,7 +419,7 @@
             folder.onEditorAction(
                 Mockito.mock(TextView::class.java),
                 EditorInfo.IME_ACTION_DONE,
-                Mockito.mock(KeyEvent::class.java)
+                Mockito.mock(KeyEvent::class.java),
             )
 
         assertTrue(result)
@@ -427,7 +434,7 @@
             folder.onEditorAction(
                 Mockito.mock(TextView::class.java),
                 EditorInfo.IME_ACTION_NONE,
-                Mockito.mock(KeyEvent::class.java)
+                Mockito.mock(KeyEvent::class.java),
             )
 
         assertFalse(result)
@@ -824,7 +831,7 @@
         val items =
             arrayListOf<ItemInfo>(
                 Mockito.mock(ItemInfo::class.java),
-                Mockito.mock(ItemInfo::class.java)
+                Mockito.mock(ItemInfo::class.java),
             )
         val view1 = Mockito.mock(View::class.java)
         val view2 = Mockito.mock(View::class.java)
@@ -845,7 +852,7 @@
         val items =
             arrayListOf<ItemInfo>(
                 Mockito.mock(ItemInfo::class.java),
-                Mockito.mock(ItemInfo::class.java)
+                Mockito.mock(ItemInfo::class.java),
             )
         val view1 = Mockito.mock(View::class.java)
         val view2 = Mockito.mock(View::class.java)
@@ -867,7 +874,7 @@
         val items =
             arrayListOf<ItemInfo>(
                 Mockito.mock(ItemInfo::class.java),
-                Mockito.mock(ItemInfo::class.java)
+                Mockito.mock(ItemInfo::class.java),
             )
         val view1 = Mockito.mock(View::class.java)
         val view2 = Mockito.mock(View::class.java)
@@ -887,7 +894,7 @@
         val items =
             arrayListOf<ItemInfo>(
                 Mockito.mock(ItemInfo::class.java),
-                Mockito.mock(ItemInfo::class.java)
+                Mockito.mock(ItemInfo::class.java),
             )
         val view1 = Mockito.mock(View::class.java)
         val view2 = Mockito.mock(View::class.java)
@@ -908,7 +915,7 @@
         val items =
             arrayListOf<ItemInfo>(
                 Mockito.mock(ItemInfo::class.java),
-                Mockito.mock(ItemInfo::class.java)
+                Mockito.mock(ItemInfo::class.java),
             )
         val view1 = Mockito.mock(View::class.java)
         val view2 = Mockito.mock(View::class.java)
diff --git a/tests/multivalentTests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt b/tests/multivalentTests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt
index 111ffaa..7f481b7 100644
--- a/tests/multivalentTests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt
@@ -18,6 +18,7 @@
 
 import android.R
 import android.content.Context
+import android.graphics.Bitmap
 import android.os.Process
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -26,9 +27,9 @@
 import com.android.launcher3.LauncherPrefs.Companion.THEMED_ICONS
 import com.android.launcher3.LauncherPrefs.Companion.get
 import com.android.launcher3.graphics.PreloadIconDrawable
-import com.android.launcher3.icons.BaseIconFactory
 import com.android.launcher3.icons.FastBitmapDrawable
 import com.android.launcher3.icons.UserBadgeDrawable
+import com.android.launcher3.icons.mono.MonoThemedBitmap
 import com.android.launcher3.model.data.FolderInfo
 import com.android.launcher3.model.data.ItemInfo
 import com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_ARCHIVED
@@ -88,54 +89,26 @@
         // Use getAppContents() to "cast" contents to WorkspaceItemInfo so we can set bitmaps
         val folderApps = modelHelper.bgDataModel.collections.valueAt(0).getAppContents()
         // Set first icon to be themed.
-        folderApps[0]
-            .bitmap
-            .setMonoIcon(
+        folderApps[0].bitmap.themedBitmap =
+            MonoThemedBitmap(
                 folderApps[0].bitmap.icon,
-                BaseIconFactory(
-                    context,
-                    context.resources.configuration.densityDpi,
-                    previewItemManager.mIconSize,
-                ),
+                Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888),
             )
 
         // Set second icon to be non-themed.
-        folderApps[1]
-            .bitmap
-            .setMonoIcon(
-                null,
-                BaseIconFactory(
-                    context,
-                    context.resources.configuration.densityDpi,
-                    previewItemManager.mIconSize,
-                ),
-            )
+        folderApps[1].bitmap.themedBitmap = null
 
         // Set third icon to be themed with badge.
-        folderApps[2]
-            .bitmap
-            .setMonoIcon(
+        folderApps[2].bitmap.themedBitmap =
+            MonoThemedBitmap(
                 folderApps[2].bitmap.icon,
-                BaseIconFactory(
-                    context,
-                    context.resources.configuration.densityDpi,
-                    previewItemManager.mIconSize,
-                ),
+                Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888),
             )
         folderApps[2].bitmap = folderApps[2].bitmap.withFlags(profileFlagOp(UserIconInfo.TYPE_WORK))
 
         // Set fourth icon to be non-themed with badge.
         folderApps[3].bitmap = folderApps[3].bitmap.withFlags(profileFlagOp(UserIconInfo.TYPE_WORK))
-        folderApps[3]
-            .bitmap
-            .setMonoIcon(
-                null,
-                BaseIconFactory(
-                    context,
-                    context.resources.configuration.densityDpi,
-                    previewItemManager.mIconSize,
-                ),
-            )
+        folderApps[3].bitmap.themedBitmap = null
 
         defaultThemedIcons = get(context).get(THEMED_ICONS)
     }
diff --git a/tests/multivalentTests/src/com/android/launcher3/icons/IconCacheTest.java b/tests/multivalentTests/src/com/android/launcher3/icons/IconCacheTest.java
index 9b4bd71..9026748 100644
--- a/tests/multivalentTests/src/com/android/launcher3/icons/IconCacheTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/icons/IconCacheTest.java
@@ -21,6 +21,7 @@
 
 import static com.android.launcher3.icons.IconCache.EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE;
 import static com.android.launcher3.icons.IconCacheUpdateHandlerTestKt.waitForUpdateHandlerToFinish;
+import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG;
 import static com.android.launcher3.model.data.AppInfo.makeLaunchIntent;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 import static com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY;
@@ -164,7 +165,7 @@
         WorkspaceItemInfo info = new WorkspaceItemInfo();
         info.intent = makeLaunchIntent(cn);
         runOnExecutorSync(MODEL_EXECUTOR,
-                () -> mIconCache.getTitleAndIcon(info, lai, false));
+                () -> mIconCache.getTitleAndIcon(info, lai, DEFAULT_LOOKUP_FLAG));
         assertNotNull(info.bitmap);
         assertFalse(info.bitmap.isLowRes());
 
diff --git a/tests/multivalentTests/src/com/android/launcher3/icons/cache/CacheLookupFlagTest.kt b/tests/multivalentTests/src/com/android/launcher3/icons/cache/CacheLookupFlagTest.kt
new file mode 100644
index 0000000..7b1851d
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/icons/cache/CacheLookupFlagTest.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.icons.cache
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.icons.cache.CacheLookupFlag.Companion.DEFAULT_LOOKUP_FLAG
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CacheLookupFlagTest {
+
+    @Test
+    fun `useLowRes preserves lowRes values`() {
+        assertFalse(DEFAULT_LOOKUP_FLAG.useLowRes())
+        assertTrue(DEFAULT_LOOKUP_FLAG.withUseLowRes().useLowRes())
+        assertFalse(DEFAULT_LOOKUP_FLAG.withUseLowRes().withUseLowRes(false).useLowRes())
+        assertTrue(
+            DEFAULT_LOOKUP_FLAG.withUseLowRes().withUseLowRes(false).withUseLowRes().useLowRes()
+        )
+    }
+
+    @Test
+    fun `usePackageIcon preserves lowRes values`() {
+        assertFalse(DEFAULT_LOOKUP_FLAG.usePackageIcon())
+        assertTrue(DEFAULT_LOOKUP_FLAG.withUsePackageIcon().usePackageIcon())
+        assertFalse(
+            DEFAULT_LOOKUP_FLAG.withUsePackageIcon().withUsePackageIcon(false).usePackageIcon()
+        )
+        assertTrue(
+            DEFAULT_LOOKUP_FLAG.withUsePackageIcon()
+                .withUsePackageIcon(false)
+                .withUsePackageIcon()
+                .usePackageIcon()
+        )
+    }
+
+    @Test
+    fun `skipAddToMemCache preserves lowRes values`() {
+        assertFalse(DEFAULT_LOOKUP_FLAG.skipAddToMemCache())
+        assertTrue(DEFAULT_LOOKUP_FLAG.withSkipAddToMemCache().skipAddToMemCache())
+        assertFalse(
+            DEFAULT_LOOKUP_FLAG.withSkipAddToMemCache()
+                .withSkipAddToMemCache(false)
+                .skipAddToMemCache()
+        )
+        assertTrue(
+            DEFAULT_LOOKUP_FLAG.withSkipAddToMemCache()
+                .withSkipAddToMemCache(false)
+                .withSkipAddToMemCache()
+                .skipAddToMemCache()
+        )
+    }
+
+    @Test
+    fun `preserves multiple flags`() {
+        val flag = DEFAULT_LOOKUP_FLAG.withSkipAddToMemCache().withUseLowRes()
+
+        assertTrue(flag.skipAddToMemCache())
+        assertTrue(flag.useLowRes())
+        assertFalse(flag.usePackageIcon())
+    }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/icons/mono/MonoIconThemeControllerTest.kt b/tests/multivalentTests/src/com/android/launcher3/icons/mono/MonoIconThemeControllerTest.kt
new file mode 100644
index 0000000..4af564e
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/icons/mono/MonoIconThemeControllerTest.kt
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.icons.mono
+
+import android.graphics.Color
+import android.graphics.drawable.AdaptiveIconDrawable
+import android.graphics.drawable.ColorDrawable
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import android.platform.uiautomatorhelpers.DeviceHelpers.context
+import android.util.DisplayMetrics
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.Flags
+import com.android.launcher3.icons.BaseIconFactory
+import com.android.launcher3.icons.BitmapInfo
+import com.android.launcher3.util.LauncherMultivalentJUnit.Companion.isRunningInRobolectric
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertNull
+import org.junit.Assume.assumeFalse
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class MonoIconThemeControllerTest {
+
+    @get:Rule val mSetFlagsRule = SetFlagsRule()
+
+    private val iconFactory = BaseIconFactory(context, DisplayMetrics.DENSITY_MEDIUM, 30)
+
+    @Test
+    fun `createThemedBitmap when mono drawable is present`() {
+        val icon = AdaptiveIconDrawable(ColorDrawable(Color.BLACK), null, ColorDrawable(Color.RED))
+        assertNotNull(
+            MonoIconThemeController().createThemedBitmap(icon, BitmapInfo.LOW_RES_INFO, iconFactory)
+        )
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_FORCE_MONOCHROME_APP_ICONS)
+    fun `createThemedBitmap when mono generation is disabled`() {
+        val icon = AdaptiveIconDrawable(ColorDrawable(Color.BLACK), null, null)
+        assertNull(
+            MonoIconThemeController().createThemedBitmap(icon, BitmapInfo.LOW_RES_INFO, iconFactory)
+        )
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_FORCE_MONOCHROME_APP_ICONS)
+    fun `createThemedBitmap when mono generation is enabled`() {
+        ensureBitmapSerializationSupported()
+        val icon = AdaptiveIconDrawable(ColorDrawable(Color.BLACK), null, null)
+        assertNotNull(
+            MonoIconThemeController().createThemedBitmap(icon, BitmapInfo.LOW_RES_INFO, iconFactory)
+        )
+    }
+
+    @Test
+    fun `decode bitmap after serialization valid data`() {
+        ensureBitmapSerializationSupported()
+        val icon = AdaptiveIconDrawable(ColorDrawable(Color.BLACK), null, ColorDrawable(Color.RED))
+        val iconInfo = iconFactory.createBadgedIconBitmap(icon)
+
+        val themeBitmap =
+            MonoIconThemeController().createThemedBitmap(icon, iconInfo, iconFactory)!!
+        assertNotNull(
+            MonoIconThemeController().decode(themeBitmap.serialize(), iconInfo, iconFactory)
+        )
+    }
+
+    @Test
+    fun `decode bitmap after serialization invalid data`() {
+        ensureBitmapSerializationSupported()
+        val icon = AdaptiveIconDrawable(ColorDrawable(Color.BLACK), null, ColorDrawable(Color.RED))
+        val iconInfo = iconFactory.createBadgedIconBitmap(icon)
+        assertNull(MonoIconThemeController().decode(byteArrayOf(1, 1, 1, 1), iconInfo, iconFactory))
+    }
+
+    @Test
+    fun `createThemedAdaptiveIcon with monochrome drawable`() {
+        val icon = AdaptiveIconDrawable(ColorDrawable(Color.BLACK), null, ColorDrawable(Color.RED))
+        assertNotNull(MonoIconThemeController().createThemedAdaptiveIcon(context, icon, null))
+    }
+
+    @Test
+    fun `createThemedAdaptiveIcon with bitmap info`() {
+        val icon = AdaptiveIconDrawable(ColorDrawable(Color.BLACK), null, ColorDrawable(Color.RED))
+        val iconInfo = iconFactory.createBadgedIconBitmap(icon)
+        iconInfo.themedBitmap =
+            MonoIconThemeController().createThemedBitmap(icon, iconInfo, iconFactory)
+
+        val nonMonoIcon = AdaptiveIconDrawable(ColorDrawable(Color.BLACK), null, null)
+        assertNotNull(
+            MonoIconThemeController().createThemedAdaptiveIcon(context, nonMonoIcon, iconInfo)
+        )
+    }
+
+    @Test
+    fun `createThemedAdaptiveIcon invalid bitmap info`() {
+        val nonMonoIcon = AdaptiveIconDrawable(ColorDrawable(Color.BLACK), null, null)
+        assertNull(
+            MonoIconThemeController()
+                .createThemedAdaptiveIcon(context, nonMonoIcon, BitmapInfo.LOW_RES_INFO)
+        )
+    }
+
+    companion object {
+
+        fun ensureBitmapSerializationSupported() {
+            // Robolectric doesn't support serializing 8-bit bitmaps
+            assumeFalse(isRunningInRobolectric)
+        }
+    }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/icons/mono/MonoThemedBitmapTest.kt b/tests/multivalentTests/src/com/android/launcher3/icons/mono/MonoThemedBitmapTest.kt
new file mode 100644
index 0000000..32de9e9
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/icons/mono/MonoThemedBitmapTest.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.icons.mono
+
+import android.graphics.Bitmap
+import android.platform.uiautomatorhelpers.DeviceHelpers.context
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.icons.BitmapInfo
+import com.android.launcher3.icons.mono.MonoIconThemeControllerTest.Companion.ensureBitmapSerializationSupported
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class MonoThemedBitmapTest {
+
+    @Test
+    fun `newDrawable returns valid drawable`() {
+        val bitmap =
+            MonoThemedBitmap(
+                Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8),
+                Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888),
+            )
+        val d = bitmap.newDrawable(BitmapInfo.LOW_RES_INFO, context)
+        assertTrue(d is ThemedIconDrawable)
+    }
+
+    @Test
+    fun `serialize returns valid bytes`() {
+        ensureBitmapSerializationSupported()
+        val bitmap =
+            MonoThemedBitmap(
+                Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8),
+                Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888),
+            )
+        assertTrue(bitmap.serialize().isNotEmpty())
+    }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/FirstScreenBroadcastHelperTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/FirstScreenBroadcastHelperTest.kt
index 9cc380e..db37247 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/FirstScreenBroadcastHelperTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/model/FirstScreenBroadcastHelperTest.kt
@@ -20,7 +20,7 @@
 import android.content.ComponentName
 import android.content.Intent
 import android.content.pm.PackageInstaller.SessionInfo
-import android.os.UserHandle
+import android.os.Process.myUserHandle
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP
@@ -77,16 +77,18 @@
             SessionInfo().apply {
                 installerPackageName = expectedInstallerPackage
                 appPackageName = expectedAppPackage
+                userId = myUserHandle().identifier
             }
         val sessionInfoUnexpected =
             SessionInfo().apply {
                 installerPackageName = expectedInstallerPackage
                 appPackageName = unexpectedAppPackage
+                userId = myUserHandle().identifier
             }
         val sessionInfoMap: HashMap<PackageUserKey, SessionInfo> =
             hashMapOf(
-                PackageUserKey(unexpectedAppPackage, UserHandle(0)) to sessionInfoExpected,
-                PackageUserKey(expectedAppPackage, UserHandle(0)) to sessionInfoUnexpected,
+                PackageUserKey(unexpectedAppPackage, myUserHandle()) to sessionInfoExpected,
+                PackageUserKey(expectedAppPackage, myUserHandle()) to sessionInfoUnexpected,
             )
 
         // When
@@ -200,16 +202,18 @@
             SessionInfo().apply {
                 installerPackageName = expectedInstallerPackage
                 appPackageName = expectedAppPackage
+                userId = myUserHandle().identifier
             }
         val sessionInfoUnexpected =
             SessionInfo().apply {
                 installerPackageName = expectedInstallerPackage
                 appPackageName = unexpectedAppPackage
+                userId = myUserHandle().identifier
             }
         val sessionInfoMap: HashMap<PackageUserKey, SessionInfo> =
             hashMapOf(
-                PackageUserKey(unexpectedAppPackage, UserHandle(0)) to sessionInfoExpected,
-                PackageUserKey(expectedAppPackage, UserHandle(0)) to sessionInfoUnexpected,
+                PackageUserKey(unexpectedAppPackage, myUserHandle()) to sessionInfoExpected,
+                PackageUserKey(expectedAppPackage, myUserHandle()) to sessionInfoUnexpected,
             )
         val expectedItemInfo = WorkspaceItemInfo().apply { intent = expectedIntent }
         val expectedFolderInfo = FolderInfo().apply { add(expectedItemInfo) }
diff --git a/tests/src/com/android/launcher3/model/PackageUpdatedTaskTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/PackageUpdatedTaskTest.kt
similarity index 98%
rename from tests/src/com/android/launcher3/model/PackageUpdatedTaskTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/model/PackageUpdatedTaskTest.kt
index d9af07a..eec6eed 100644
--- a/tests/src/com/android/launcher3/model/PackageUpdatedTaskTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/model/PackageUpdatedTaskTest.kt
@@ -21,7 +21,7 @@
 import android.content.pm.ApplicationInfo
 import android.content.pm.LauncherActivityInfo
 import android.content.pm.LauncherApps
-import android.os.UserHandle
+import android.os.Process.myUserHandle
 import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.SetFlagsRule
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -60,7 +60,7 @@
 
     @get:Rule val setFlagsRule = SetFlagsRule()
 
-    private val mUser = UserHandle(0)
+    private val mUser = myUserHandle()
     private val mDataModel: BgDataModel = BgDataModel()
     private val mLauncherModelHelper = LauncherModelHelper()
     private val mContext: SandboxModelContext = spy(mLauncherModelHelper.sandboxContext)
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt
index 7099d38..d699eee 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt
@@ -393,6 +393,39 @@
     }
 
     @Test
+    fun `When Pinned Deep Shortcut is not stored in ShortcutManager re-query by Shortcut ID`() {
+        // Given
+        mockCursor.itemType = ITEM_TYPE_DEEP_SHORTCUT
+        val si =
+            mock<ShortcutInfo>().apply {
+                whenever(id).thenReturn("")
+                whenever(`package`).thenReturn("")
+                whenever(activity).thenReturn(mock())
+                whenever(longLabel).thenReturn("")
+                whenever(isEnabled).thenReturn(true)
+                whenever(disabledMessage).thenReturn("")
+                whenever(disabledReason).thenReturn(0)
+                whenever(persons).thenReturn(EMPTY_PERSON_ARRAY)
+            }
+        whenever(mockLauncherApps.getShortcuts(any(), any())).thenReturn(listOf(si))
+        mKeyToPinnedShortcutsMap.clear()
+        mIconRequestInfos = mutableListOf()
+
+        // When
+        itemProcessorUnderTest =
+            createWorkspaceItemProcessorUnderTest(allDeepShortcuts = mAllDeepShortcuts)
+        itemProcessorUnderTest.processItem()
+
+        // Then
+        verify(mockLauncherApps).getShortcuts(any(), any())
+        assertWithMessage("item restoreFlag should be set to 0")
+            .that(mockCursor.restoreFlag)
+            .isEqualTo(0)
+        verify(mockCursor).markRestored()
+        verify(mockCursor).checkAndAddItem(any(), any(), anyOrNull())
+    }
+
+    @Test
     fun `When Pinned Deep Shortcut not found then mark deleted`() {
 
         // Given
diff --git a/tests/src/com/android/launcher3/model/gridmigration/GridMigrationUtils.kt b/tests/multivalentTests/src/com/android/launcher3/model/gridmigration/GridMigrationUtils.kt
similarity index 98%
rename from tests/src/com/android/launcher3/model/gridmigration/GridMigrationUtils.kt
rename to tests/multivalentTests/src/com/android/launcher3/model/gridmigration/GridMigrationUtils.kt
index cc8e61d..174d716 100644
--- a/tests/src/com/android/launcher3/model/gridmigration/GridMigrationUtils.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/model/gridmigration/GridMigrationUtils.kt
@@ -24,8 +24,11 @@
 
 class MockSet(override val size: Int) : Set<String> {
     override fun contains(element: String): Boolean = true
+
     override fun containsAll(elements: Collection<String>): Boolean = true
+
     override fun isEmpty(): Boolean = false
+
     override fun iterator(): Iterator<String> = listOf<String>().iterator()
 }
 
@@ -91,7 +94,7 @@
                 appWidgetProvider = cursor.getString(indexWidgetProvider),
                 intent = cursor.getString(indexIntent),
                 type = cursor.getInt(indexItemType),
-                container = cursor.getInt(container)
+                container = cursor.getInt(container),
             )
         )
     }
diff --git a/tests/src/com/android/launcher3/model/gridmigration/ValidGridMigrationUnitTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/gridmigration/ValidGridMigrationUnitTest.kt
similarity index 100%
rename from tests/src/com/android/launcher3/model/gridmigration/ValidGridMigrationUnitTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/model/gridmigration/ValidGridMigrationUnitTest.kt
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/DisplayControllerTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/DisplayControllerTest.kt
index a3a680e..d0cf610 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/DisplayControllerTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/util/DisplayControllerTest.kt
@@ -15,7 +15,6 @@
  */
 package com.android.launcher3.util
 
-import android.content.Context
 import android.content.res.Configuration
 import android.content.res.Resources
 import android.graphics.Point
@@ -26,18 +25,22 @@
 import android.view.Display
 import android.view.Surface
 import androidx.test.annotation.UiThreadTest
-import androidx.test.core.app.ApplicationProvider
 import androidx.test.filters.SmallTest
 import com.android.launcher3.LauncherPrefs
 import com.android.launcher3.LauncherPrefs.Companion.TASKBAR_PINNING
 import com.android.launcher3.LauncherPrefs.Companion.TASKBAR_PINNING_IN_DESKTOP_MODE
+import com.android.launcher3.dagger.LauncherAppComponent
+import com.android.launcher3.dagger.LauncherAppModule
+import com.android.launcher3.dagger.LauncherAppSingleton
 import com.android.launcher3.util.DisplayController.CHANGE_DENSITY
 import com.android.launcher3.util.DisplayController.CHANGE_ROTATION
 import com.android.launcher3.util.DisplayController.CHANGE_TASKBAR_PINNING
 import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener
-import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext
+import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext
 import com.android.launcher3.util.window.CachedDisplayInfo
 import com.android.launcher3.util.window.WindowManagerProxy
+import dagger.BindsInstance
+import dagger.Component
 import junit.framework.Assert.assertFalse
 import junit.framework.Assert.assertTrue
 import kotlin.math.min
@@ -51,6 +54,7 @@
 import org.mockito.kotlin.doReturn
 import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
 import org.mockito.stubbing.Answer
@@ -60,12 +64,10 @@
 @RunWith(LauncherMultivalentJUnit::class)
 class DisplayControllerTest {
 
-    private val appContext: Context = ApplicationProvider.getApplicationContext()
-
-    private val context: SandboxContext = mock()
-    private val windowManagerProxy: WindowManagerProxy = mock()
+    private val context = spy(SandboxModelContext())
+    private val windowManagerProxy: MyWmProxy = mock()
     private val launcherPrefs: LauncherPrefs = mock()
-    private val displayManager: DisplayManager = mock()
+    private lateinit var displayManager: DisplayManager
     private val display: Display = mock()
     private val resources: Resources = mock()
     private val displayInfoChangeListener: DisplayInfoChangeListener = mock()
@@ -85,7 +87,7 @@
             WindowBounds(Rect(0, 0, height, width), Rect(0, inset, 0, 0), Surface.ROTATION_270),
         )
     private val configuration =
-        Configuration(appContext.resources.configuration).apply {
+        Configuration(context.resources.configuration).apply {
             densityDpi = this@DisplayControllerTest.densityDpi
             screenWidthDp = (bounds[0].bounds.width() / density).toInt()
             screenHeightDp = (bounds[0].bounds.height() / density).toInt()
@@ -94,8 +96,12 @@
 
     @Before
     fun setUp() {
-        whenever(context.getObject(eq(WindowManagerProxy.INSTANCE))).thenReturn(windowManagerProxy)
-        whenever(context.getObject(eq(LauncherPrefs.INSTANCE))).thenReturn(launcherPrefs)
+        context.initDaggerComponent(
+            DaggerDisplayControllerTestComponent.builder().bindWMProxy(windowManagerProxy)
+        )
+        context.putObject(LauncherPrefs.INSTANCE, launcherPrefs)
+        displayManager = context.spyService(DisplayManager::class.java)
+
         whenever(launcherPrefs.get(TASKBAR_PINNING)).thenReturn(false)
         whenever(launcherPrefs.get(TASKBAR_PINNING_IN_DESKTOP_MODE)).thenReturn(true)
 
@@ -118,15 +124,13 @@
 
         whenever(windowManagerProxy.getNavigationMode(any())).thenReturn(NavigationMode.NO_BUTTON)
         // Mock context
-        whenever(context.createWindowContext(any(), any(), anyOrNull())).thenReturn(context)
-        whenever(context.getSystemService(eq(DisplayManager::class.java)))
-            .thenReturn(displayManager)
+        doReturn(context).whenever(context).createWindowContext(any(), any(), anyOrNull())
         doNothing().whenever(context).registerComponentCallbacks(any())
 
         // Mock display
         whenever(display.rotation).thenReturn(displayInfo.rotation)
-        whenever(context.display).thenReturn(display)
-        whenever(displayManager.getDisplay(any())).thenReturn(display)
+        doReturn(display).whenever(context).display
+        doReturn(display).whenever(displayManager).getDisplay(any())
 
         // Mock resources
         doReturn(context).whenever(context).applicationContext
@@ -143,6 +147,7 @@
         // We need to reset the taskbar mode preference override even if a test throws an exception.
         // Otherwise, it may break the following tests' assumptions.
         DisplayController.enableTaskbarModePreferenceForTests(false)
+        context.onDestroy()
     }
 
     @Test
@@ -225,3 +230,19 @@
         assertFalse(displayController.getInfo().isTransientTaskbar())
     }
 }
+
+class MyWmProxy : WindowManagerProxy()
+
+@LauncherAppSingleton
+@Component(modules = [LauncherAppModule::class])
+interface DisplayControllerTestComponent : LauncherAppComponent {
+
+    override fun getWmProxy(): MyWmProxy
+
+    @Component.Builder
+    interface Builder : LauncherAppComponent.Builder {
+        @BindsInstance fun bindWMProxy(proxy: MyWmProxy): Builder
+
+        override fun build(): DisplayControllerTestComponent
+    }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/WidgetUtils.java b/tests/multivalentTests/src/com/android/launcher3/util/WidgetUtils.java
index deb0ef3..a87a208 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/WidgetUtils.java
+++ b/tests/multivalentTests/src/com/android/launcher3/util/WidgetUtils.java
@@ -21,6 +21,7 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.os.Bundle;
+import android.os.Process;
 
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
@@ -86,6 +87,7 @@
     public static AppWidgetProviderInfo createAppWidgetProviderInfo(ComponentName cn) {
         ActivityInfo activityInfo = new ActivityInfo();
         activityInfo.applicationInfo = new ApplicationInfo();
+        activityInfo.applicationInfo.uid = Process.myUid();
         AppWidgetProviderInfo info = new AppWidgetProviderInfo();
         info.providerInfo = activityInfo;
         info.provider = cn;
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/GeneratedPreviewTest.kt b/tests/multivalentTests/src/com/android/launcher3/widget/GeneratedPreviewTest.kt
index ac5fda2..b92582c 100644
--- a/tests/multivalentTests/src/com/android/launcher3/widget/GeneratedPreviewTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/GeneratedPreviewTest.kt
@@ -1,9 +1,8 @@
 package com.android.launcher3.widget
 
+import android.appwidget.AppWidgetManager
 import android.appwidget.AppWidgetProviderInfo
 import android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN
-import android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD
-import android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX
 import android.content.ComponentName
 import android.content.Context
 import android.content.pm.ActivityInfo
@@ -14,23 +13,30 @@
 import android.view.ContextThemeWrapper
 import android.view.LayoutInflater
 import android.widget.RemoteViews
-import androidx.test.core.app.ApplicationProvider.getApplicationContext
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
 import com.android.launcher3.Flags.FLAG_ENABLE_GENERATED_PREVIEWS
 import com.android.launcher3.InvariantDeviceProfile
+import com.android.launcher3.R
 import com.android.launcher3.icons.IconCache
-import com.android.launcher3.icons.IconProvider
 import com.android.launcher3.model.WidgetItem
 import com.android.launcher3.util.ActivityContextWrapper
 import com.android.launcher3.util.Executors
+import com.android.launcher3.util.Executors.MAIN_EXECUTOR
+import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext
+import com.android.launcher3.util.TestUtil
 import com.google.common.truth.Truth.assertThat
 import org.junit.After
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.whenever
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
@@ -46,28 +52,25 @@
         getInstrumentation().context.run {
             resources.getIdentifier("test_layout_appwidget_blue", "layout", packageName)
         }
-    private lateinit var context: Context
+
+    private lateinit var context: SandboxModelContext
+    private lateinit var uiContext: Context
     private lateinit var generatedPreview: RemoteViews
     private lateinit var widgetCell: WidgetCell
-    private lateinit var helper: WidgetManagerHelper
     private lateinit var appWidgetProviderInfo: LauncherAppWidgetProviderInfo
     private lateinit var widgetItem: WidgetItem
-    private lateinit var iconCache: IconCache
+
+    @Mock lateinit var iconCache: IconCache
 
     @Before
     fun setup() {
-        context = getApplicationContext()
+        MockitoAnnotations.initMocks(this)
+        context = SandboxModelContext()
         generatedPreview = RemoteViews(context.packageName, generatedPreviewLayout)
+        uiContext =
+            ActivityContextWrapper(ContextThemeWrapper(context, R.style.WidgetContainerTheme))
         widgetCell =
-            LayoutInflater.from(
-                    ActivityContextWrapper(
-                        ContextThemeWrapper(
-                            context,
-                            com.android.launcher3.R.style.WidgetContainerTheme,
-                        )
-                    )
-                )
-                .inflate(com.android.launcher3.R.layout.widget_cell, null) as WidgetCell
+            LayoutInflater.from(uiContext).inflate(R.layout.widget_cell, null) as WidgetCell
         appWidgetProviderInfo =
             AppWidgetProviderInfo()
                 .apply {
@@ -76,72 +79,52 @@
                     providerInfo = ActivityInfo().apply { applicationInfo = ApplicationInfo() }
                 }
                 .let { LauncherAppWidgetProviderInfo.fromProviderInfo(context, it) }
-        helper =
-            object : WidgetManagerHelper(context) {
-                override fun loadGeneratedPreview(
-                    info: AppWidgetProviderInfo,
-                    widgetCategory: Int,
-                ) =
-                    generatedPreview.takeIf {
-                        info === appWidgetProviderInfo &&
-                            widgetCategory == WIDGET_CATEGORY_HOME_SCREEN
-                    }
+
+        val widgetManager = context.spyService(AppWidgetManager::class.java)
+        doAnswer { i ->
+                generatedPreview.takeIf {
+                    i.arguments[0] == appWidgetProviderInfo.provider &&
+                        i.arguments[1] == appWidgetProviderInfo.user &&
+                        i.arguments[2] == WIDGET_CATEGORY_HOME_SCREEN
+                }
             }
+            .whenever(widgetManager)
+            .getWidgetPreview(any(), any(), any())
         createWidgetItem()
     }
 
     @After
     fun tearDown() {
-        iconCache.close()
+        context.destroy()
     }
 
     private fun createWidgetItem() {
         Executors.MODEL_EXECUTOR.submit {
                 val idp = InvariantDeviceProfile()
-                if (::iconCache.isInitialized) iconCache.close()
-                iconCache = IconCache(context, idp, null, IconProvider(context))
-                widgetItem = WidgetItem(appWidgetProviderInfo, idp, iconCache, context, helper)
+                widgetItem = WidgetItem(appWidgetProviderInfo, idp, iconCache, context)
             }
             .get()
     }
 
     @Test
-    fun widgetItem_hasGeneratedPreview() {
-        assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_HOME_SCREEN)).isTrue()
-        assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_KEYGUARD)).isFalse()
-        assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_SEARCHBOX)).isFalse()
-    }
-
-    @Test
     fun widgetItem_hasGeneratedPreview_noPreview() {
         appWidgetProviderInfo.generatedPreviewCategories = 0
         createWidgetItem()
-        assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_HOME_SCREEN)).isFalse()
-        assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_KEYGUARD)).isFalse()
-        assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_SEARCHBOX)).isFalse()
-    }
-
-    @Test
-    fun widgetItem_hasGeneratedPreview_nullPreview() {
-        appWidgetProviderInfo.generatedPreviewCategories =
-            WIDGET_CATEGORY_HOME_SCREEN or WIDGET_CATEGORY_KEYGUARD
-        createWidgetItem()
-        assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_HOME_SCREEN)).isTrue()
-        // loadGeneratedPreview returns null for KEYGUARD, so this should still be false.
-        assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_KEYGUARD)).isFalse()
-        assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_SEARCHBOX)).isFalse()
+        val preview = DatabaseWidgetPreviewLoader(uiContext).generatePreviewInfoBg(widgetItem, 1, 1)
+        assertThat(preview.remoteViews).isNull()
     }
 
     @Test
     fun widgetItem_getGeneratedPreview() {
-        val preview = widgetItem.generatedPreviews.get(WIDGET_CATEGORY_HOME_SCREEN)
-        assertThat(preview).isEqualTo(generatedPreview)
+        val preview = DatabaseWidgetPreviewLoader(uiContext).generatePreviewInfoBg(widgetItem, 1, 1)
+        assertThat(preview.remoteViews).isEqualTo(generatedPreview)
     }
 
     @Test
     fun widgetCell_showGeneratedPreview() {
         widgetCell.applyFromCellItem(widgetItem)
-        DatabaseWidgetPreviewLoader.getLoaderExecutor().submit {}.get()
+        TestUtil.runOnExecutorSync(DatabaseWidgetPreviewLoader.getLoaderExecutor()) {}
+        TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {}
         assertThat(widgetCell.appWidgetHostViewPreview).isNotNull()
         assertThat(widgetCell.appWidgetHostViewPreview?.appWidgetInfo)
             .isEqualTo(appWidgetProviderInfo)
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProviderTest.java b/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProviderTest.java
index 8b6553f..ac67d2b 100644
--- a/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProviderTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProviderTest.java
@@ -148,11 +148,12 @@
 
         doAnswer(invocation -> widgetLabel).when(mIconCache).getTitleNoCache(any());
 
-        AppWidgetProviderInfo providerInfo = WidgetUtils.createAppWidgetProviderInfo(ComponentName
-                .createRelative(TEST_PACKAGE, widgetClassName));
+        AppWidgetProviderInfo providerInfo = WidgetUtils.createAppWidgetProviderInfo(
+                ComponentName.createRelative(TEST_PACKAGE, widgetClassName));
 
         LauncherAppWidgetProviderInfo launcherAppWidgetProviderInfo =
-                LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, providerInfo);
+                spy(LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, providerInfo));
+        doReturn(Process.myUserHandle()).when(launcherAppWidgetProviderInfo).getProfile();
         launcherAppWidgetProviderInfo.spanX = 2;
         launcherAppWidgetProviderInfo.spanY = 2;
         launcherAppWidgetProviderInfo.label = widgetLabel;
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java b/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
index 0d9464a..86bbcc1 100644
--- a/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
@@ -54,7 +54,6 @@
 import com.android.launcher3.widget.DatabaseWidgetPreviewLoader;
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.widget.WidgetCell;
-import com.android.launcher3.widget.WidgetManagerHelper;
 import com.android.launcher3.widget.model.WidgetsListContentEntry;
 
 import org.junit.Before;
@@ -143,7 +142,6 @@
     }
 
     private List<WidgetItem> generateWidgetItems(String packageName, int numOfWidgets) {
-        WidgetManagerHelper widgetManager = new WidgetManagerHelper(mContext);
         ArrayList<WidgetItem> widgetItems = new ArrayList<>();
         for (int i = 0; i < numOfWidgets; i++) {
             ComponentName cn = ComponentName.createRelative(packageName, ".SampleWidget" + i);
@@ -151,7 +149,7 @@
 
             widgetItems.add(new WidgetItem(
                     LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, widgetInfo),
-                    mTestProfile, mIconCache, mContext, widgetManager));
+                    mTestProfile, mIconCache, mContext));
         }
         return widgetItems;
     }
diff --git a/tests/src/com/android/launcher3/compat/PromiseIconUiTest.java b/tests/src/com/android/launcher3/compat/PromiseIconUiTest.java
index 34b292c..f494f38 100644
--- a/tests/src/com/android/launcher3/compat/PromiseIconUiTest.java
+++ b/tests/src/com/android/launcher3/compat/PromiseIconUiTest.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.compat;
 
+import static android.os.Process.myUserHandle;
+
 import static com.android.launcher3.Flags.FLAG_ENABLE_SUPPORT_FOR_ARCHIVING;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -136,7 +138,8 @@
     public void testPromiseIcon_addedArchivedApp() throws Throwable {
         installDummyAppAndWaitForUIUpdate();
         assertThat(executeShellCommand(
-                String.format("pm archive %s", DUMMY_PACKAGE)))
+                String.format("pm archive --user %d %s",
+                        myUserHandle().getIdentifier(), DUMMY_PACKAGE)))
                 .isEqualTo("Success\n");
 
         // Create and add test session
diff --git a/tests/src/com/android/launcher3/model/LoaderTaskTest.kt b/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
index 882061f..4d181ff 100644
--- a/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
+++ b/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
@@ -2,6 +2,7 @@
 
 import android.appwidget.AppWidgetManager
 import android.content.Intent
+import android.os.Process
 import android.os.UserHandle
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
@@ -15,6 +16,9 @@
 import com.android.launcher3.LauncherAppState
 import com.android.launcher3.LauncherModel
 import com.android.launcher3.LauncherModel.LoaderTransaction
+import com.android.launcher3.LauncherPrefs
+import com.android.launcher3.LauncherPrefs.Companion.IS_FIRST_LOAD_AFTER_RESTORE
+import com.android.launcher3.LauncherPrefs.Companion.RESTORE_DEVICE
 import com.android.launcher3.icons.IconCache
 import com.android.launcher3.icons.cache.CachingLogic
 import com.android.launcher3.icons.cache.IconCacheUpdateHandler
@@ -129,6 +133,8 @@
 
     @After
     fun tearDown() {
+        LauncherPrefs.get(context).removeSync(RESTORE_DEVICE)
+        LauncherPrefs.get(context).putSync(IS_FIRST_LOAD_AFTER_RESTORE.to(false))
         context.onDestroy()
         mockitoSession.finishMocking()
     }
@@ -136,7 +142,7 @@
     @Test
     fun loadsDataProperly() =
         with(BgDataModel()) {
-            val MAIN_HANDLE = UserHandle.of(0)
+            val MAIN_HANDLE = Process.myUserHandle()
             val mockUserHandles = arrayListOf<UserHandle>(MAIN_HANDLE)
             `when`(userCache.userProfiles).thenReturn(mockUserHandles)
             `when`(userCache.getUserInfo(MAIN_HANDLE)).thenReturn(UserIconInfo(MAIN_HANDLE, 1))
@@ -186,7 +192,7 @@
     fun setsQuietModeFlagCorrectlyForWorkProfile() =
         with(BgDataModel()) {
             setFlagsRule.enableFlags(Flags.FLAG_ENABLE_PRIVATE_SPACE)
-            val MAIN_HANDLE = UserHandle.of(0)
+            val MAIN_HANDLE = Process.myUserHandle()
             val mockUserHandles = arrayListOf<UserHandle>(MAIN_HANDLE)
             `when`(userCache.userProfiles).thenReturn(mockUserHandles)
             `when`(userManagerState?.isUserQuiet(MAIN_HANDLE)).thenReturn(true)
@@ -215,7 +221,7 @@
     fun setsQuietModeFlagCorrectlyForPrivateProfile() =
         with(BgDataModel()) {
             setFlagsRule.enableFlags(Flags.FLAG_ENABLE_PRIVATE_SPACE)
-            val MAIN_HANDLE = UserHandle.of(0)
+            val MAIN_HANDLE = Process.myUserHandle()
             val mockUserHandles = arrayListOf<UserHandle>(MAIN_HANDLE)
             `when`(userCache.userProfiles).thenReturn(mockUserHandles)
             `when`(userManagerState?.isUserQuiet(MAIN_HANDLE)).thenReturn(true)
@@ -241,84 +247,8 @@
         }
 
     @Test
-    @DisableFlags(Flags.FLAG_ENABLE_FIRST_SCREEN_BROADCAST_ARCHIVING_EXTRAS)
-    fun `When secure setting true and is restore then send installed item broadcast`() {
-        // Given
-        val spyContext = spy(context)
-        `when`(app.context).thenReturn(spyContext)
-        whenever(
-                FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast(
-                    any(),
-                    any(),
-                    any(),
-                    any(),
-                )
-            )
-            .thenReturn(listOf(expectedBroadcastModel))
-
-        whenever(
-                FirstScreenBroadcastHelper.sendBroadcastsForModels(
-                    spyContext,
-                    listOf(expectedBroadcastModel),
-                )
-            )
-            .thenCallRealMethod()
-
-        Settings.Secure.putInt(spyContext.contentResolver, "launcher_broadcast_installed_apps", 1)
-        RestoreDbTask.setPending(spyContext)
-
-        // When
-        LoaderTask(
-                app,
-                bgAllAppsList,
-                BgDataModel(),
-                modelDelegate,
-                launcherBinder,
-                widgetsFilterDataProvider,
-            )
-            .runSyncOnBackgroundThread()
-
-        // Then
-        val argumentCaptor = ArgumentCaptor.forClass(Intent::class.java)
-        verify(spyContext).sendBroadcast(argumentCaptor.capture())
-        val actualBroadcastIntent = argumentCaptor.value
-        assertEquals(expectedBroadcastModel.installerPackage, actualBroadcastIntent.`package`)
-        assertEquals(
-            ArrayList(expectedBroadcastModel.installedWorkspaceItems),
-            actualBroadcastIntent.getStringArrayListExtra("workspaceInstalledItems"),
-        )
-        assertEquals(
-            ArrayList(expectedBroadcastModel.installedHotseatItems),
-            actualBroadcastIntent.getStringArrayListExtra("hotseatInstalledItems"),
-        )
-        assertEquals(
-            ArrayList(
-                expectedBroadcastModel.firstScreenInstalledWidgets +
-                    expectedBroadcastModel.secondaryScreenInstalledWidgets
-            ),
-            actualBroadcastIntent.getStringArrayListExtra("widgetInstalledItems"),
-        )
-        assertEquals(
-            ArrayList(expectedBroadcastModel.pendingCollectionItems),
-            actualBroadcastIntent.getStringArrayListExtra("folderItem"),
-        )
-        assertEquals(
-            ArrayList(expectedBroadcastModel.pendingWorkspaceItems),
-            actualBroadcastIntent.getStringArrayListExtra("workspaceItem"),
-        )
-        assertEquals(
-            ArrayList(expectedBroadcastModel.pendingHotseatItems),
-            actualBroadcastIntent.getStringArrayListExtra("hotseatItem"),
-        )
-        assertEquals(
-            ArrayList(expectedBroadcastModel.pendingWidgetItems),
-            actualBroadcastIntent.getStringArrayListExtra("widgetItem"),
-        )
-    }
-
-    @Test
     @EnableFlags(Flags.FLAG_ENABLE_FIRST_SCREEN_BROADCAST_ARCHIVING_EXTRAS)
-    fun `When broadcast flag true and is restore then send installed item broadcast`() {
+    fun `When broadcast flag on and is restore and secure setting off then send new broadcast`() {
         // Given
         val spyContext = spy(context)
         `when`(app.context).thenReturn(spyContext)
@@ -416,7 +346,7 @@
             )
             .thenCallRealMethod()
 
-        Settings.Secure.putInt(spyContext.contentResolver, "launcher_broadcast_installed_apps", 1)
+        Settings.Secure.putInt(spyContext.contentResolver, "launcher_broadcast_installed_apps", 0)
 
         // When
         LoaderTask(
@@ -435,7 +365,7 @@
 
     @Test
     @DisableFlags(Flags.FLAG_ENABLE_FIRST_SCREEN_BROADCAST_ARCHIVING_EXTRAS)
-    fun `When broadcast flag and secure setting false then installed item broadcast not sent`() {
+    fun `When broadcast flag off then installed item broadcast not sent`() {
         // Given
         val spyContext = spy(context)
         `when`(app.context).thenReturn(spyContext)
@@ -457,7 +387,57 @@
             )
             .thenCallRealMethod()
 
-        Settings.Secure.putInt(spyContext.contentResolver, "launcher_broadcast_installed_apps", 0)
+        Settings.Secure.putInt(
+            spyContext.contentResolver,
+            "disable_launcher_broadcast_installed_apps",
+            0,
+        )
+        RestoreDbTask.setPending(spyContext)
+
+        // When
+        LoaderTask(
+                app,
+                bgAllAppsList,
+                BgDataModel(),
+                modelDelegate,
+                launcherBinder,
+                widgetsFilterDataProvider,
+            )
+            .runSyncOnBackgroundThread()
+
+        // Then
+        verify(spyContext, times(0)).sendBroadcast(any())
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_FIRST_SCREEN_BROADCAST_ARCHIVING_EXTRAS)
+    fun `When failsafe secure setting on then installed item broadcast not sent`() {
+        // Given
+        val spyContext = spy(context)
+        `when`(app.context).thenReturn(spyContext)
+        whenever(
+                FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast(
+                    any(),
+                    any(),
+                    any(),
+                    any(),
+                )
+            )
+            .thenReturn(listOf(expectedBroadcastModel))
+
+        whenever(
+                FirstScreenBroadcastHelper.sendBroadcastsForModels(
+                    spyContext,
+                    listOf(expectedBroadcastModel),
+                )
+            )
+            .thenCallRealMethod()
+
+        Settings.Secure.putInt(
+            spyContext.contentResolver,
+            "disable_launcher_broadcast_installed_apps",
+            1,
+        )
         RestoreDbTask.setPending(spyContext)
 
         // When
diff --git a/tests/src/com/android/launcher3/pm/InstallSessionHelperTest.kt b/tests/src/com/android/launcher3/pm/InstallSessionHelperTest.kt
index ca2ef42..94b2c7e 100644
--- a/tests/src/com/android/launcher3/pm/InstallSessionHelperTest.kt
+++ b/tests/src/com/android/launcher3/pm/InstallSessionHelperTest.kt
@@ -23,6 +23,7 @@
 import android.content.pm.PackageInstaller
 import android.content.pm.PackageManager
 import android.graphics.Bitmap
+import android.os.Process.myUserHandle
 import android.os.UserHandle
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -73,14 +74,14 @@
                 sessionId = 0
                 installerPackageName = expectedInstallerPackage
                 appPackageName = expectedAppPackage
-                userId = 0
+                userId = myUserHandle().identifier
             }
         val expectedVerifiedSession2 =
             PackageInstaller.SessionInfo().apply {
                 sessionId = 1
                 installerPackageName = expectedInstallerPackage
                 appPackageName = "app2"
-                userId = 0
+                userId = myUserHandle().identifier
             }
         val expectedSessions = listOf(expectedVerifiedSession1, expectedVerifiedSession2)
         whenever(launcherApps.allPackageInstallerSessions).thenReturn(expectedSessions)
@@ -97,13 +98,13 @@
             PackageInstaller.SessionInfo().apply {
                 installerPackageName = expectedInstallerPackage
                 appPackageName = expectedAppPackage
-                userId = 0
+                userId = myUserHandle().identifier
             }
         whenever(launcherApps.allPackageInstallerSessions)
             .thenReturn(listOf(expectedVerifiedSession))
         // When
         val actualSession =
-            installSessionHelper.getActiveSessionInfo(UserHandle(0), expectedAppPackage)
+            installSessionHelper.getActiveSessionInfo(myUserHandle(), expectedAppPackage)
         // Then
         assertThat(actualSession).isEqualTo(expectedVerifiedSession)
     }
@@ -116,7 +117,7 @@
                 sessionId = 1
                 installerPackageName = expectedInstallerPackage
                 appPackageName = expectedAppPackage
-                userId = 0
+                userId = myUserHandle().identifier
             }
         whenever(mockPackageInstaller.getSessionInfo(1)).thenReturn(expectedSession)
         // When
@@ -147,14 +148,14 @@
                 sessionId = 0
                 installerPackageName = expectedInstallerPackage
                 appPackageName = expectedAppPackage
-                userId = 0
+                userId = myUserHandle().identifier
             }
         val expectedVerifiedSession2 =
             PackageInstaller.SessionInfo().apply {
                 sessionId = 1
                 installerPackageName = expectedInstallerPackage
                 appPackageName = "app2"
-                userId = 0
+                userId = myUserHandle().identifier
             }
         val expectedSessions = listOf(expectedVerifiedSession1, expectedVerifiedSession2)
         whenever(launcherApps.allPackageInstallerSessions).thenReturn(expectedSessions)
@@ -174,7 +175,7 @@
                 sessionId = 1
                 installerPackageName = expectedInstallerPackage
                 appPackageName = "app2"
-                userId = 0
+                userId = myUserHandle().identifier
             }
         whenever(launcherApps.allPackageInstallerSessions).thenReturn(listOf(expectedSession))
         // When
@@ -196,7 +197,7 @@
                 sessionId = 1
                 installerPackageName = expectedInstallerPackage
                 appPackageName = "app2"
-                userId = 0
+                userId = myUserHandle().identifier
             }
         whenever(launcherApps.allPackageInstallerSessions).thenReturn(listOf(expectedSession))
         // When
@@ -219,7 +220,7 @@
                 sessionId = 1
                 installerPackageName = expectedInstallerPackage
                 appPackageName = "appPackage"
-                userId = 0
+                userId = myUserHandle().identifier
                 appIcon = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8)
                 appLabel = "appLabel"
                 installReason = PackageManager.INSTALL_REASON_USER
@@ -249,7 +250,7 @@
                 sessionId = 1
                 installerPackageName = expectedInstallerPackage
                 appPackageName = "appPackage"
-                userId = 0
+                userId = myUserHandle().identifier
                 appIcon = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8)
                 appLabel = "appLabel"
                 installReason = PackageManager.INSTALL_REASON_USER
diff --git a/tests/src/com/android/launcher3/popup/SystemShortcutTest.java b/tests/src/com/android/launcher3/popup/SystemShortcutTest.java
index ae54e95..075f667 100644
--- a/tests/src/com/android/launcher3/popup/SystemShortcutTest.java
+++ b/tests/src/com/android/launcher3/popup/SystemShortcutTest.java
@@ -62,15 +62,12 @@
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.R;
 import com.android.launcher3.allapps.PrivateProfileManager;
-import com.android.launcher3.dagger.LauncherAppComponent;
-import com.android.launcher3.dagger.LauncherAppSingleton;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.logging.StatsLogManager.StatsLogger;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.pm.UserCache;
-import com.android.launcher3.util.ApiWrapper;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext;
 import com.android.launcher3.util.LauncherMultivalentJUnit;
@@ -81,9 +78,6 @@
 import com.android.launcher3.widget.picker.model.WidgetPickerDataProvider;
 import com.android.launcher3.widget.picker.model.data.WidgetPickerData;
 
-import dagger.BindsInstance;
-import dagger.Component;
-
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
@@ -109,7 +103,6 @@
     private AppInfo mAppInfo;
 
     @Mock UserCache mUserCache;
-    @Mock ApiWrapper mApiWrapper;
     @Mock UserIconInfo mUserIconInfo;
     @Mock LauncherActivityInfo mLauncherActivityInfo;
     @Mock ApplicationInfo mApplicationInfo;
@@ -120,9 +113,6 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mSandboxContext.initDaggerComponent(
-                DaggerSystemShortcutTest_TestComponent.builder().bindApiWrapper(
-                        ApiWrapper.INSTANCE.get(mSandboxContext)));
         mSandboxContext.putObject(UserCache.INSTANCE, mUserCache);
         mTestContext = new TestSandboxModelContextWrapper(mSandboxContext) {
             @Override
@@ -412,16 +402,4 @@
         systemShortcut.onClick(mView);
         verify(mSandboxContext).startActivity(any());
     }
-
-    @LauncherAppSingleton
-    @Component
-    interface TestComponent extends LauncherAppComponent {
-        @Component.Builder
-        interface Builder extends LauncherAppComponent.Builder {
-            @BindsInstance Builder bindApiWrapper(ApiWrapper wrapper);
-
-            @Override
-            TestComponent build();
-        }
-    }
 }
diff --git a/tests/src/com/android/launcher3/ui/BaseLauncherTaplTest.java b/tests/src/com/android/launcher3/ui/BaseLauncherTaplTest.java
index 8449853..9fa1500 100644
--- a/tests/src/com/android/launcher3/ui/BaseLauncherTaplTest.java
+++ b/tests/src/com/android/launcher3/ui/BaseLauncherTaplTest.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3.ui;
 
+import static android.os.Process.myUserHandle;
 import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
@@ -219,7 +220,8 @@
 
     protected void clearPackageData(String pkg) throws IOException, InterruptedException {
         assertTrue("pm clear command failed",
-                mDevice.executeShellCommand("pm clear " + pkg)
+                mDevice.executeShellCommand(
+                        String.format("pm clear --user %d %s", myUserHandle().getIdentifier(), pkg))
                         .contains("Success"));
         assertTrue("pm wait-for-handler command failed",
                 mDevice.executeShellCommand("pm wait-for-handler")
diff --git a/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java b/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
index e5c5c19..d49168f 100644
--- a/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
+++ b/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
@@ -1,11 +1,16 @@
 package com.android.launcher3.ui;
 
+import static com.android.launcher3.LauncherPrefs.FIXED_LANDSCAPE_MODE;
+
 import android.util.Log;
 import android.view.Surface;
 
+import com.android.launcher3.Flags;
 import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.tapl.TestHelpers;
 import com.android.launcher3.util.rule.FailureWatcher;
+import com.android.launcher3.util.window.WindowManagerProxy;
 
 import org.junit.rules.TestRule;
 import org.junit.runner.Description;
@@ -67,9 +72,11 @@
                     Log.e(TAG, "Error", e);
                     throw e;
                 } finally {
+
                     mTest.mDevice.setOrientationNatural();
                     mTest.executeOnLauncher(launcher ->
                     {
+                        LauncherPrefs.get(launcher).put(FIXED_LANDSCAPE_MODE, false);
                         if (launcher != null) {
                             launcher.getRotationHelper().forceAllowRotationForTesting(false);
                         }
@@ -90,6 +97,13 @@
             }
 
             private void evaluateInLandscape() throws Throwable {
+                if (Flags.oneGridSpecs()
+                        && WindowManagerProxy.INSTANCE.get(mTest.mTargetContext)
+                        .isTaskbarDrawnInProcess()) {
+                    mTest.executeOnLauncher(launcher -> LauncherPrefs.get(launcher)
+                            .put(FIXED_LANDSCAPE_MODE, true)
+                    );
+                }
                 mTest.mDevice.setOrientationLeft();
                 mTest.mLauncher.setExpectedRotation(Surface.ROTATION_90);
                 AbstractLauncherUiTest.checkDetectedLeaks(mTest.mLauncher, true);
diff --git a/tests/src/com/android/launcher3/ui/WorkProfileTest.java b/tests/src/com/android/launcher3/ui/WorkProfileTest.java
index d866a9f..b6c1135 100644
--- a/tests/src/com/android/launcher3/ui/WorkProfileTest.java
+++ b/tests/src/com/android/launcher3/ui/WorkProfileTest.java
@@ -26,6 +26,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
+import android.os.Process;
 import android.util.Log;
 import android.view.View;
 
@@ -73,7 +74,9 @@
 
     @Before
     public void setUp() throws Exception {
-        String output = executeShellCommand("pm create-user --profileOf 0 --managed TestProfile");
+        String output = executeShellCommand(String.format(
+                "pm create-user --profileOf %d --managed TestProfile",
+                Process.myUserHandle().getIdentifier()));
         updateWorkProfileSetupSuccessful("pm create-user", output);
 
         String[] tokens = output.split("\\s+");
@@ -146,7 +149,7 @@
         executeOnLauncher(l -> {
             // Ensure updates are not deferred so notification happens when apps pause.
             l.getAppsView().getAppsStore().disableDeferUpdates(DEFER_UPDATES_TEST);
-            l.getAppsView().getWorkManager().getWorkUtilityView().performClick();
+            l.getAppsView().getWorkManager().getWorkUtilityView().getWorkFAB().performClick();
         });
 
         waitForLauncherCondition("Work profile toggle OFF failed", launcher -> {
diff --git a/tests/src/com/android/launcher3/ui/workspace/ThemeIconsTest.java b/tests/src/com/android/launcher3/ui/workspace/ThemeIconsTest.java
index c623513..c852729 100644
--- a/tests/src/com/android/launcher3/ui/workspace/ThemeIconsTest.java
+++ b/tests/src/com/android/launcher3/ui/workspace/ThemeIconsTest.java
@@ -37,7 +37,7 @@
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.allapps.AllAppsRecyclerView;
 import com.android.launcher3.celllayout.FavoriteItemsTransaction;
-import com.android.launcher3.icons.ThemedIconDrawable;
+import com.android.launcher3.icons.mono.ThemedIconDrawable;
 import com.android.launcher3.popup.ArrowPopup;
 import com.android.launcher3.util.BaseLauncherActivityTest;
 import com.android.launcher3.util.Executors;
diff --git a/tests/src/com/android/launcher3/util/rule/ShellCommandRule.java b/tests/src/com/android/launcher3/util/rule/ShellCommandRule.java
index 2219003..d093bf7 100644
--- a/tests/src/com/android/launcher3/util/rule/ShellCommandRule.java
+++ b/tests/src/com/android/launcher3/util/rule/ShellCommandRule.java
@@ -21,9 +21,9 @@
 
 import android.content.ComponentName;
 import android.content.pm.ActivityInfo;
+import android.os.Process;
 
 import androidx.annotation.Nullable;
-import androidx.test.InstrumentationRegistry;
 import androidx.test.uiautomator.UiDevice;
 
 import com.android.systemui.shared.system.PackageManagerWrapper;
@@ -91,8 +91,9 @@
      * Grants the launcher permission to bind widgets.
      */
     public static ShellCommandRule grantWidgetBind() {
-        return new ShellCommandRule("appwidget grantbind --package "
-                + InstrumentationRegistry.getTargetContext().getPackageName(), null);
+        return new ShellCommandRule(String.format("appwidget grantbind --package %s --user %d",
+                getInstrumentation().getTargetContext().getPackageName(),
+                Process.myUserHandle().getIdentifier()), null);
     }
 
     /**
@@ -109,8 +110,9 @@
     }
 
     public static String getLauncherCommand(ActivityInfo launcher) {
-        return "cmd package set-home-activity " +
-                new ComponentName(launcher.packageName, launcher.name).flattenToString();
+        return String.format("cmd package set-home-activity --user %d %s",
+                Process.myUserHandle().getIdentifier(),
+                new ComponentName(launcher.packageName, launcher.name).flattenToString());
     }
 
     /**
diff --git a/tests/tapl/com/android/launcher3/tapl/TestHelpers.java b/tests/tapl/com/android/launcher3/tapl/TestHelpers.java
index 7f6062f..b43bfcf 100644
--- a/tests/tapl/com/android/launcher3/tapl/TestHelpers.java
+++ b/tests/tapl/com/android/launcher3/tapl/TestHelpers.java
@@ -33,10 +33,9 @@
 import androidx.test.uiautomator.SearchCondition;
 import androidx.test.uiautomator.UiDevice;
 
-import org.junit.Assert;
-
 import java.util.Date;
 import java.util.List;
+import java.util.Objects;
 
 public class TestHelpers {
 
@@ -113,8 +112,8 @@
     }
 
     private static String checkCrash(Context context, String label, long startTime) {
-        DropBoxManager dropbox = (DropBoxManager) context.getSystemService(Context.DROPBOX_SERVICE);
-        Assert.assertNotNull("Unable access the DropBoxManager service", dropbox);
+        DropBoxManager dropbox = Objects.requireNonNull(
+                context.getSystemService(DropBoxManager.class));
 
         long timestamp = startTime;
         DropBoxManager.Entry entry;