Merge "[Test week] add tests for InstallSessionHelper" into main
diff --git a/go/quickstep/res/values/styles.xml b/go/quickstep/res/values/styles.xml
index c659331..2524c76 100644
--- a/go/quickstep/res/values/styles.xml
+++ b/go/quickstep/res/values/styles.xml
@@ -16,7 +16,7 @@
 -->
 <resources>
     <!-- App themes -->
-    <style name="LauncherTheme" parent="@style/BaseLauncherTheme">
+    <style name="LauncherTheme" parent="@style/DynamicColorsBaseLauncherTheme">
         <item name="overviewButtonTextColor">@color/go_overview_text_color</item>
         <item name="overviewButtonIconColor">@color/go_overview_text_color</item>
         <item name="overviewButtonBackgroundColor">@color/go_overview_button_color</item>
diff --git a/quickstep/res/color/all_set_bg_primary.xml b/quickstep/res/color/all_set_bg_primary.xml
index 4cf857d..013de7a 100644
--- a/quickstep/res/color/all_set_bg_primary.xml
+++ b/quickstep/res/color/all_set_bg_primary.xml
@@ -15,5 +15,5 @@
 -->
 <selector xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
-    <item android:color="?androidprv:attr/materialColorPrimaryContainer"/>
+    <item android:color="?attr/materialColorPrimaryContainer"/>
 </selector>
diff --git a/quickstep/res/color/all_set_bg_tertiary.xml b/quickstep/res/color/all_set_bg_tertiary.xml
index e62b094..b58d61c 100644
--- a/quickstep/res/color/all_set_bg_tertiary.xml
+++ b/quickstep/res/color/all_set_bg_tertiary.xml
@@ -15,5 +15,5 @@
 -->
 <selector xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
-    <item android:color="?androidprv:attr/materialColorTertiary"/>
+    <item android:color="?attr/materialColorTertiary"/>
 </selector>
diff --git a/quickstep/res/color/bubblebar_drop_target_bg_color.xml b/quickstep/res/color/bubblebar_drop_target_bg_color.xml
index ca37c7f..bae8c4e 100644
--- a/quickstep/res/color/bubblebar_drop_target_bg_color.xml
+++ b/quickstep/res/color/bubblebar_drop_target_bg_color.xml
@@ -15,5 +15,5 @@
   -->
 <selector xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
-    <item android:alpha="0.35" android:color="?androidprv:attr/materialColorPrimaryContainer" />
+    <item android:alpha="0.35" android:color="?attr/materialColorPrimaryContainer" />
 </selector>
\ No newline at end of file
diff --git a/quickstep/res/color/menu_item_hover_state_color.xml b/quickstep/res/color/menu_item_hover_state_color.xml
index 1dd7654..3c68789 100644
--- a/quickstep/res/color/menu_item_hover_state_color.xml
+++ b/quickstep/res/color/menu_item_hover_state_color.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <selector xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
-    <item android:state_hovered="false" android:color="?androidprv:attr/materialColorSurfaceBright" />
-    <item android:state_hovered="true" android:color="?androidprv:attr/materialColorSurfaceVariant" />
+    <item android:state_hovered="false" android:color="?attr/materialColorSurfaceBright" />
+    <item android:state_hovered="true" android:color="?attr/materialColorSurfaceVariant" />
 </selector>
\ No newline at end of file
diff --git a/quickstep/res/drawable/bg_bubble_bar_drop_target.xml b/quickstep/res/drawable/bg_bubble_bar_drop_target.xml
index 79e4318..f597cb5 100644
--- a/quickstep/res/drawable/bg_bubble_bar_drop_target.xml
+++ b/quickstep/res/drawable/bg_bubble_bar_drop_target.xml
@@ -20,5 +20,5 @@
     <solid android:color="@color/bubblebar_drop_target_bg_color" />
     <stroke
         android:width="1dp"
-        android:color="?androidprv:attr/materialColorPrimaryContainer" />
+        android:color="?attr/materialColorPrimaryContainer" />
 </shape>
diff --git a/quickstep/res/drawable/bg_bubble_expanded_view_drop_target.xml b/quickstep/res/drawable/bg_bubble_expanded_view_drop_target.xml
index d722dd7..169e396 100644
--- a/quickstep/res/drawable/bg_bubble_expanded_view_drop_target.xml
+++ b/quickstep/res/drawable/bg_bubble_expanded_view_drop_target.xml
@@ -22,6 +22,6 @@
         <solid android:color="@color/bubblebar_drop_target_bg_color" />
         <stroke
             android:width="1dp"
-            android:color="?androidprv:attr/materialColorPrimaryContainer" />
+            android:color="?attr/materialColorPrimaryContainer" />
     </shape>
 </inset>
diff --git a/quickstep/res/drawable/bg_floating_desktop_select.xml b/quickstep/res/drawable/bg_floating_desktop_select.xml
index d7df338..6481be4 100644
--- a/quickstep/res/drawable/bg_floating_desktop_select.xml
+++ b/quickstep/res/drawable/bg_floating_desktop_select.xml
@@ -19,5 +19,5 @@
     android:shape="rectangle">
 
     <corners android:radius="@dimen/rounded_button_radius" />
-    <solid android:color="?androidprv:attr/materialColorPrimaryContainer" />
+    <solid android:color="?attr/materialColorPrimaryContainer" />
 </shape>
\ No newline at end of file
diff --git a/quickstep/res/drawable/bg_overview_clear_all_button.xml b/quickstep/res/drawable/bg_overview_clear_all_button.xml
index 143761f..0d12274 100644
--- a/quickstep/res/drawable/bg_overview_clear_all_button.xml
+++ b/quickstep/res/drawable/bg_overview_clear_all_button.xml
@@ -21,7 +21,7 @@
         <shape android:shape="rectangle"
             android:tint="?colorButtonNormal">
             <corners android:radius="@dimen/recents_clear_all_outline_radius" />
-            <solid android:color="?androidprv:attr/materialColorSurfaceBright"/>
+            <solid android:color="?attr/materialColorSurfaceBright"/>
         </shape>
     </item>
 </ripple>
\ No newline at end of file
diff --git a/quickstep/res/drawable/bg_taskbar_edu_tooltip.xml b/quickstep/res/drawable/bg_taskbar_edu_tooltip.xml
index 20c524d..9e9bb2b 100644
--- a/quickstep/res/drawable/bg_taskbar_edu_tooltip.xml
+++ b/quickstep/res/drawable/bg_taskbar_edu_tooltip.xml
@@ -18,5 +18,5 @@
     android:shape="rectangle">
 
     <corners android:radius="@dimen/dialogCornerRadius" />
-    <solid android:color="?androidprv:attr/materialColorSurfaceBright"/>
+    <solid android:color="?attr/materialColorSurfaceBright"/>
 </shape>
\ No newline at end of file
diff --git a/quickstep/res/drawable/bg_wellbeing_toast.xml b/quickstep/res/drawable/bg_wellbeing_toast.xml
index 0b66849..418caae 100644
--- a/quickstep/res/drawable/bg_wellbeing_toast.xml
+++ b/quickstep/res/drawable/bg_wellbeing_toast.xml
@@ -16,6 +16,6 @@
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     android:shape="rectangle">
-    <solid android:color="?androidprv:attr/materialColorSecondaryFixed" />
+    <solid android:color="?attr/materialColorSecondaryFixed" />
     <corners android:radius="?android:attr/dialogCornerRadius" />
 </shape>
\ No newline at end of file
diff --git a/quickstep/res/drawable/ic_chevron_down.xml b/quickstep/res/drawable/ic_chevron_down.xml
index f246cbc..b586e50 100644
--- a/quickstep/res/drawable/ic_chevron_down.xml
+++ b/quickstep/res/drawable/ic_chevron_down.xml
@@ -19,7 +19,7 @@
     android:width="48dp"
     android:height="48dp"
     android:autoMirrored="true"
-    android:tint="?androidprv:attr/materialColorOnSurface"
+    android:tint="?attr/materialColorOnSurface"
     android:viewportHeight="48"
     android:viewportWidth="48">
     <group
diff --git a/quickstep/res/drawable/keyboard_quick_switch_overview_button_background.xml b/quickstep/res/drawable/keyboard_quick_switch_overview_button_background.xml
index 2a4f087..8180293 100644
--- a/quickstep/res/drawable/keyboard_quick_switch_overview_button_background.xml
+++ b/quickstep/res/drawable/keyboard_quick_switch_overview_button_background.xml
@@ -17,6 +17,6 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     android:shape="rectangle">
-    <solid android:color="?androidprv:attr/materialColorSurfaceBright" />
+    <solid android:color="?attr/materialColorSurfaceBright" />
     <corners android:radius="@dimen/keyboard_quick_switch_task_view_radius" />
 </shape>
diff --git a/quickstep/res/drawable/rotate_tutorial_warning.xml b/quickstep/res/drawable/rotate_tutorial_warning.xml
index d1117a4..90b7d64 100644
--- a/quickstep/res/drawable/rotate_tutorial_warning.xml
+++ b/quickstep/res/drawable/rotate_tutorial_warning.xml
@@ -21,6 +21,6 @@
     android:viewportHeight="960"
     android:viewportWidth="960">
     <path
-        android:fillColor="?androidprv:attr/materialColorOnSurface"
+        android:fillColor="?attr/materialColorOnSurface"
         android:pathData="M40,840L480,80L920,840L40,840ZM178,760L782,760L480,240L178,760ZM480,720Q497,720 508.5,708.5Q520,697 520,680Q520,663 508.5,651.5Q497,640 480,640Q463,640 451.5,651.5Q440,663 440,680Q440,697 451.5,708.5Q463,720 480,720ZM440,600L520,600L520,400L440,400L440,600ZM480,500L480,500L480,500L480,500Z" />
 </vector>
diff --git a/quickstep/res/layout-land/keyboard_quick_switch_taskview.xml b/quickstep/res/layout-land/keyboard_quick_switch_taskview.xml
index 38df756..613edac 100644
--- a/quickstep/res/layout-land/keyboard_quick_switch_taskview.xml
+++ b/quickstep/res/layout-land/keyboard_quick_switch_taskview.xml
@@ -23,7 +23,7 @@
     android:importantForAccessibility="yes"
     android:background="@drawable/keyboard_quick_switch_task_view_background"
     android:clipToOutline="true"
-    launcher:focusBorderColor="?androidprv:attr/materialColorOutline">
+    launcher:focusBorderColor="?attr/materialColorOutline">
 
     <androidx.constraintlayout.widget.ConstraintLayout
         android:id="@+id/content"
diff --git a/quickstep/res/layout-sw600dp-land/gesture_tutorial_step_menu.xml b/quickstep/res/layout-sw600dp-land/gesture_tutorial_step_menu.xml
index e4942ae..40d2322 100644
--- a/quickstep/res/layout-sw600dp-land/gesture_tutorial_step_menu.xml
+++ b/quickstep/res/layout-sw600dp-land/gesture_tutorial_step_menu.xml
@@ -20,7 +20,7 @@
     android:theme="@style/GestureTutorialActivity"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:background="?androidprv:attr/materialColorSurfaceContainer"
+    android:background="?attr/materialColorSurfaceContainer"
     android:fitsSystemWindows="true">
 
     <androidx.constraintlayout.widget.ConstraintLayout
@@ -163,7 +163,7 @@
             android:layout_marginVertical="16dp"
             android:text="@string/gesture_tutorial_action_button_label"
             android:background="@drawable/gesture_tutorial_action_button_background"
-            android:backgroundTint="?androidprv:attr/materialColorPrimary"
+            android:backgroundTint="?attr/materialColorPrimary"
             android:stateListAnimator="@null"
 
             app:layout_constraintTop_toBottomOf="@id/guideline"
diff --git a/quickstep/res/layout/digital_wellbeing_toast.xml b/quickstep/res/layout/digital_wellbeing_toast.xml
index 9144c7f..6a99a3b 100644
--- a/quickstep/res/layout/digital_wellbeing_toast.xml
+++ b/quickstep/res/layout/digital_wellbeing_toast.xml
@@ -24,7 +24,7 @@
     android:forceHasOverlappingRendering="false"
     android:gravity="center"
     android:importantForAccessibility="noHideDescendants"
-    android:textColor="?androidprv:attr/materialColorOnSecondaryFixed"
+    android:textColor="?attr/materialColorOnSecondaryFixed"
     android:textSize="14sp"
     android:autoSizeTextType="uniform"
     android:autoSizeMaxTextSize="14sp"/>
\ No newline at end of file
diff --git a/quickstep/res/layout/gesture_tutorial_step_menu.xml b/quickstep/res/layout/gesture_tutorial_step_menu.xml
index 668a2e1..7feb882 100644
--- a/quickstep/res/layout/gesture_tutorial_step_menu.xml
+++ b/quickstep/res/layout/gesture_tutorial_step_menu.xml
@@ -20,7 +20,7 @@
     android:theme="@style/GestureTutorialActivity"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:background="?androidprv:attr/materialColorSurfaceContainer"
+    android:background="?attr/materialColorSurfaceContainer"
     android:fitsSystemWindows="true">
 
     <androidx.constraintlayout.widget.ConstraintLayout
@@ -161,7 +161,7 @@
             android:layout_marginVertical="16dp"
             android:text="@string/gesture_tutorial_action_button_label"
             android:background="@drawable/gesture_tutorial_action_button_background"
-            android:backgroundTint="?androidprv:attr/materialColorPrimary"
+            android:backgroundTint="?attr/materialColorPrimary"
             android:stateListAnimator="@null"
 
             app:layout_constraintTop_toBottomOf="@id/guideline"
diff --git a/quickstep/res/layout/icon_app_chip_view.xml b/quickstep/res/layout/icon_app_chip_view.xml
index fb9bf99..36ece2a 100644
--- a/quickstep/res/layout/icon_app_chip_view.xml
+++ b/quickstep/res/layout/icon_app_chip_view.xml
@@ -26,7 +26,7 @@
     android:importantForAccessibility="no"
     android:autoMirrored="true"
     android:elevation="@dimen/task_thumbnail_icon_menu_elevation"
-    android:background="?androidprv:attr/materialColorSurfaceBright">
+    android:background="?attr/materialColorSurfaceBright">
 
     <!-- ignoring warning because the user of the anchor is a Rect where RTL is not needed -->
     <!-- This anchor's bounds is in the expected location after rotations and translations are
diff --git a/quickstep/res/layout/keyboard_quick_switch_taskview.xml b/quickstep/res/layout/keyboard_quick_switch_taskview.xml
index c0ace9a..8f09176 100644
--- a/quickstep/res/layout/keyboard_quick_switch_taskview.xml
+++ b/quickstep/res/layout/keyboard_quick_switch_taskview.xml
@@ -23,7 +23,7 @@
     android:importantForAccessibility="yes"
     android:background="@drawable/keyboard_quick_switch_task_view_background"
     android:clipToOutline="true"
-    launcher:focusBorderColor="?androidprv:attr/materialColorOutline">
+    launcher:focusBorderColor="?attr/materialColorOutline">
 
     <androidx.constraintlayout.widget.ConstraintLayout
         android:id="@+id/content"
diff --git a/quickstep/res/layout/keyboard_quick_switch_textonly_taskview.xml b/quickstep/res/layout/keyboard_quick_switch_textonly_taskview.xml
index e48794e..c76a2e3 100644
--- a/quickstep/res/layout/keyboard_quick_switch_textonly_taskview.xml
+++ b/quickstep/res/layout/keyboard_quick_switch_textonly_taskview.xml
@@ -22,7 +22,7 @@
     android:layout_height="@dimen/keyboard_quick_switch_taskview_height"
     android:clipToOutline="true"
     android:importantForAccessibility="yes"
-    launcher:focusBorderColor="?androidprv:attr/materialColorOutline">
+    launcher:focusBorderColor="?attr/materialColorOutline">
 
     <androidx.constraintlayout.widget.ConstraintLayout
         android:id="@+id/content"
@@ -40,7 +40,7 @@
             android:layout_width="@dimen/keyboard_quick_switch_recents_icon_size"
             android:layout_height="@dimen/keyboard_quick_switch_recents_icon_size"
             android:layout_marginBottom="8dp"
-            android:tint="?androidprv:attr/materialColorOnSurface"
+            android:tint="?attr/materialColorOnSurface"
 
             app:layout_constraintVertical_chainStyle="packed"
             app:layout_constraintTop_toTopOf="parent"
diff --git a/quickstep/res/layout/keyboard_quick_switch_view.xml b/quickstep/res/layout/keyboard_quick_switch_view.xml
index 2bba788..2420a46 100644
--- a/quickstep/res/layout/keyboard_quick_switch_view.xml
+++ b/quickstep/res/layout/keyboard_quick_switch_view.xml
@@ -43,7 +43,7 @@
             android:layout_height="@dimen/keyboard_quick_switch_no_recent_items_icon_size"
             android:layout_marginBottom="@dimen/keyboard_quick_switch_no_recent_items_icon_margin"
             android:src="@drawable/view_carousel"
-            android:tint="?androidprv:attr/materialColorOnSurface"
+            android:tint="?attr/materialColorOnSurface"
             android:importantForAccessibility="no"
 
             app:layout_constraintVertical_chainStyle="packed"
diff --git a/quickstep/res/layout/overview_clear_all_button.xml b/quickstep/res/layout/overview_clear_all_button.xml
index 3380ea4..40f38f4 100644
--- a/quickstep/res/layout/overview_clear_all_button.xml
+++ b/quickstep/res/layout/overview_clear_all_button.xml
@@ -23,6 +23,6 @@
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:text="@string/recents_clear_all"
-    android:textColor="?androidprv:attr/materialColorOnSurface"
-    launcher:focusBorderColor="?androidprv:attr/materialColorOutline"
+    android:textColor="?attr/materialColorOnSurface"
+    launcher:focusBorderColor="?attr/materialColorOutline"
     android:textSize="14sp" />
\ No newline at end of file
diff --git a/quickstep/res/layout/task.xml b/quickstep/res/layout/task.xml
index 46c1332..ac1a50a 100644
--- a/quickstep/res/layout/task.xml
+++ b/quickstep/res/layout/task.xml
@@ -25,8 +25,8 @@
     android:clipChildren="false"
     android:defaultFocusHighlightEnabled="false"
     android:focusable="true"
-    launcher:focusBorderColor="?androidprv:attr/materialColorOutline"
-    launcher:hoverBorderColor="?androidprv:attr/materialColorPrimary">
+    launcher:focusBorderColor="?attr/materialColorOutline"
+    launcher:hoverBorderColor="?attr/materialColorPrimary">
 
     <include layout="@layout/task_thumbnail_deprecated" />
 
diff --git a/quickstep/res/layout/task_desktop.xml b/quickstep/res/layout/task_desktop.xml
index 453057c..64aa7e1 100644
--- a/quickstep/res/layout/task_desktop.xml
+++ b/quickstep/res/layout/task_desktop.xml
@@ -25,8 +25,8 @@
     android:clipChildren="true"
     android:defaultFocusHighlightEnabled="false"
     android:focusable="true"
-    launcher:focusBorderColor="?androidprv:attr/materialColorOutline"
-    launcher:hoverBorderColor="?androidprv:attr/materialColorPrimary"
+    launcher:focusBorderColor="?attr/materialColorOutline"
+    launcher:hoverBorderColor="?attr/materialColorPrimary"
     android:clipToPadding="true"
     android:padding="0.1dp">
     <!-- Setting a padding of 0.1 dp since android:clipToPadding needs a non-zero value for
diff --git a/quickstep/res/layout/task_grouped.xml b/quickstep/res/layout/task_grouped.xml
index 708aa3c..da2b29f 100644
--- a/quickstep/res/layout/task_grouped.xml
+++ b/quickstep/res/layout/task_grouped.xml
@@ -30,8 +30,8 @@
     android:clipChildren="false"
     android:defaultFocusHighlightEnabled="false"
     android:focusable="true"
-    launcher:focusBorderColor="?androidprv:attr/materialColorOutline"
-    launcher:hoverBorderColor="?androidprv:attr/materialColorPrimary">
+    launcher:focusBorderColor="?attr/materialColorOutline"
+    launcher:hoverBorderColor="?attr/materialColorPrimary">
 
     <include layout="@layout/task_thumbnail_deprecated"/>
 
diff --git a/quickstep/res/layout/task_thumbnail.xml b/quickstep/res/layout/task_thumbnail.xml
index 34640e6..b1fe89e 100644
--- a/quickstep/res/layout/task_thumbnail.xml
+++ b/quickstep/res/layout/task_thumbnail.xml
@@ -23,6 +23,7 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:importantForAccessibility="no"
+        android:scaleType="matrix"
         android:visibility="gone"/>
 
     <com.android.quickstep.task.thumbnail.LiveTileView
diff --git a/quickstep/res/layout/task_view_menu_option.xml b/quickstep/res/layout/task_view_menu_option.xml
index ffe2401..5218de0 100644
--- a/quickstep/res/layout/task_view_menu_option.xml
+++ b/quickstep/res/layout/task_view_menu_option.xml
@@ -31,7 +31,7 @@
       android:layout_height="@dimen/system_shortcut_icon_size"
       android:layout_marginStart="@dimen/task_menu_option_start_margin"
       android:layout_gravity="center_horizontal"
-      android:backgroundTint="?androidprv:attr/materialColorOnSurface"/>
+      android:backgroundTint="?attr/materialColorOnSurface"/>
 
     <TextView
         style="@style/BaseIcon"
@@ -40,7 +40,7 @@
         android:layout_height="wrap_content"
         android:layout_marginStart="@dimen/task_menu_option_text_start_margin"
         android:textSize="14sp"
-        android:textColor="?androidprv:attr/materialColorOnSurface"
+        android:textColor="?attr/materialColorOnSurface"
         android:focusable="false"
         android:gravity="start"
         android:ellipsize="end" />
diff --git a/quickstep/res/values-night/colors.xml b/quickstep/res/values-night/colors.xml
index 94100ba..2052446 100644
--- a/quickstep/res/values-night/colors.xml
+++ b/quickstep/res/values-night/colors.xml
@@ -26,6 +26,6 @@
 
     <!-- Turn on work apps button -->
     <color name="work_turn_on_stroke">?androidprv:attr/colorAccentSecondaryVariant</color>
-    <color name="work_fab_bg_color">?androidprv:attr/materialColorPrimaryFixedDim</color>
-    <color name="work_fab_icon_color">?androidprv:attr/materialColorOnPrimaryFixed</color>
+    <color name="work_fab_bg_color">?attr/materialColorPrimaryFixedDim</color>
+    <color name="work_fab_icon_color">?attr/materialColorOnPrimaryFixed</color>
 </resources>
\ No newline at end of file
diff --git a/quickstep/res/values-night/styles.xml b/quickstep/res/values-night/styles.xml
index 401351f..2cb633a 100644
--- a/quickstep/res/values-night/styles.xml
+++ b/quickstep/res/values-night/styles.xml
@@ -82,16 +82,16 @@
     <style name="GestureTutorialActivity" parent="@style/AppTheme">
         <item name="background">@android:color/transparent</item>
         <item name="tutorialSubtitle">@android:color/white</item>
-        <item name="surfaceContainer">?androidprv:attr/materialColorSurfaceContainer</item>
-        <item name="onSurfaceHome">?androidprv:attr/materialColorPrimaryFixedDim</item>
-        <item name="surfaceHome">?androidprv:attr/materialColorOnPrimaryFixedVariant</item>
-        <item name="secondaryHome">?androidprv:attr/materialColorOnPrimaryFixed</item>
-        <item name="onSurfaceBack">?androidprv:attr/materialColorTertiaryFixedDim</item>
-        <item name="surfaceBack">?androidprv:attr/materialColorOnTertiaryFixedVariant</item>
-        <item name="secondaryBack">?androidprv:attr/materialColorOnTertiaryFixed</item>
-        <item name="onSurfaceOverview">?androidprv:attr/materialColorPrimaryFixed</item>
-        <item name="surfaceOverview">?androidprv:attr/materialColorOnSecondaryFixedVariant</item>
-        <item name="secondaryOverview">?androidprv:attr/materialColorOnSecondaryFixed</item>
+        <item name="surfaceContainer">?attr/materialColorSurfaceContainer</item>
+        <item name="onSurfaceHome">?attr/materialColorPrimaryFixedDim</item>
+        <item name="surfaceHome">?attr/materialColorOnPrimaryFixedVariant</item>
+        <item name="secondaryHome">?attr/materialColorOnPrimaryFixed</item>
+        <item name="onSurfaceBack">?attr/materialColorTertiaryFixedDim</item>
+        <item name="surfaceBack">?attr/materialColorOnTertiaryFixedVariant</item>
+        <item name="secondaryBack">?attr/materialColorOnTertiaryFixed</item>
+        <item name="onSurfaceOverview">?attr/materialColorPrimaryFixed</item>
+        <item name="surfaceOverview">?attr/materialColorOnSecondaryFixedVariant</item>
+        <item name="secondaryOverview">?attr/materialColorOnSecondaryFixed</item>
     </style>
 
 </resources>
\ No newline at end of file
diff --git a/quickstep/res/values/colors.xml b/quickstep/res/values/colors.xml
index 0f997f9..0bb971e 100644
--- a/quickstep/res/values/colors.xml
+++ b/quickstep/res/values/colors.xml
@@ -95,6 +95,6 @@
 
     <!-- Turn on work apps button -->
     <color name="work_turn_on_stroke">?androidprv:attr/colorAccentPrimaryVariant</color>
-    <color name="work_fab_bg_color">?androidprv:attr/materialColorPrimaryFixedDim</color>
-    <color name="work_fab_icon_color">?androidprv:attr/materialColorOnPrimaryFixed</color>
+    <color name="work_fab_bg_color">?attr/materialColorPrimaryFixedDim</color>
+    <color name="work_fab_icon_color">?attr/materialColorOnPrimaryFixed</color>
 </resources>
\ No newline at end of file
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index d4f66e2..867ce17 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -449,11 +449,13 @@
 
     <dimen name="bubblebar_icon_size_small">32dp</dimen>
     <dimen name="bubblebar_icon_size">36dp</dimen>
+    <dimen name="bubblebar_icon_size_persistent_taskbar">28dp</dimen>
     <dimen name="bubblebar_badge_size">24dp</dimen>
     <dimen name="bubblebar_icon_overlap">12dp</dimen>
     <dimen name="bubblebar_overflow_inset">16dp</dimen>
     <dimen name="bubblebar_icon_spacing">6dp</dimen>
     <dimen name="bubblebar_icon_spacing_large">8dp</dimen>
+    <dimen name="bubblebar_icon_spacing_persistent_taskbar">@dimen/bubblebar_icon_spacing</dimen>
     <dimen name="bubblebar_expanded_icon_spacing">12dp</dimen>
     <dimen name="bubblebar_icon_elevation">1dp</dimen>
 
diff --git a/quickstep/res/values/styles.xml b/quickstep/res/values/styles.xml
index 952505a..1f4720c 100644
--- a/quickstep/res/values/styles.xml
+++ b/quickstep/res/values/styles.xml
@@ -124,7 +124,7 @@
     <style name="TextAppearance.GestureTutorial.ButtonLabel"
         parent="TextAppearance.GestureTutorial.CallToAction">
         <item name="android:gravity">center</item>
-        <item name="android:textColor">?androidprv:attr/materialColorOnPrimary</item>
+        <item name="android:textColor">?attr/materialColorOnPrimary</item>
         <item name="android:letterSpacing">0.02</item>
         <item name="android:textSize">16sp</item>
         <item name="android:textAllCaps">false</item>
@@ -268,53 +268,59 @@
     <style name="KeyboardQuickSwitchText">
         <item name="fontFamily">google-sans-text</item>
         <item name="android:textSize">14sp</item>
-        <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
+        <item name="android:textColor">?attr/materialColorOnSurface</item>
         <item name="lineHeight">20sp</item>
     </style>
 
     <style name="KeyboardQuickSwitchText.OnBackground" parent="KeyboardQuickSwitchText">
-        <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
+        <item name="android:textColor">?attr/materialColorOnSurface</item>
     </style>
 
     <style name="GestureTutorialActivity" parent="@style/AppTheme">
         <item name="background">@android:color/transparent</item>
         <item name="tutorialSubtitle">@android:color/black</item>
-        <item name="surfaceContainer">?androidprv:attr/materialColorSurfaceContainer</item>
-        <item name="onSurfaceHome">?androidprv:attr/materialColorPrimaryFixed</item>
+        <item name="surfaceContainer">?attr/materialColorSurfaceContainer</item>
+        <item name="onSurfaceHome">?attr/materialColorPrimaryFixed</item>
         <item name="surfaceHome">@android:color/system_accent1_300</item>
-        <item name="secondaryHome">?androidprv:attr/materialColorOnPrimaryFixedVariant</item>
-        <item name="onSurfaceBack">?androidprv:attr/materialColorTertiaryFixed</item>
+        <item name="secondaryHome">?attr/materialColorOnPrimaryFixedVariant</item>
+        <item name="onSurfaceBack">?attr/materialColorTertiaryFixed</item>
         <item name="surfaceBack">@android:color/system_accent3_300</item>
-        <item name="secondaryBack">?androidprv:attr/materialColorOnTertiaryFixedVariant</item>
-        <item name="onSurfaceOverview">?androidprv:attr/materialColorPrimaryFixed</item>
+        <item name="secondaryBack">?attr/materialColorOnTertiaryFixedVariant</item>
+        <item name="onSurfaceOverview">?attr/materialColorPrimaryFixed</item>
         <item name="surfaceOverview">@android:color/system_accent2_300</item>
-        <item name="secondaryOverview">?androidprv:attr/materialColorOnSecondaryFixedVariant</item>
+        <item name="secondaryOverview">?attr/materialColorOnSecondaryFixedVariant</item>
     </style>
 
     <style name="rotate_prompt_title" parent="TextAppearance.GestureTutorial.Dialog.Title">
-        <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
+        <item name="android:textColor">?attr/materialColorOnSurface</item>
     </style>
 
     <style name="rotate_prompt_subtitle" parent="TextAppearance.GestureTutorial.Dialog.Subtitle">
-        <item name="android:textColor">?androidprv:attr/materialColorOnSurfaceVariant</item>
+        <item name="android:textColor">?attr/materialColorOnSurfaceVariant</item>
     </style>
 
     <style name="ArrowTipTaskbarStyle">
-        <item name="arrowTipBackground">?androidprv:attr/materialColorSurfaceContainer</item>
-        <item name="arrowTipTextColor">?androidprv:attr/materialColorOnSurface</item>
+        <item name="arrowTipBackground">?attr/materialColorSurfaceContainer</item>
+        <item name="arrowTipTextColor">?attr/materialColorOnSurface</item>
     </style>
 
     <style name="IconAppChipMenuTextStyle">
         <item name="android:fontFamily">google-sans-text-medium</item>
         <item name="android:textSize">@dimen/task_thumbnail_icon_menu_text_size</item>
-        <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
+        <item name="android:textColor">?attr/materialColorOnSurface</item>
         <item name="android:letterSpacing">0.025</item>
         <item name="android:lineHeight">20sp</item>
     </style>
 
-    <style name="WidgetPickerActivityTheme" parent="@android:style/Theme.Translucent.NoTitleBar">
-        <item name="widgetsTheme">@style/WidgetContainerTheme</item>
+    <style name="WidgetPickerActivityTheme" parent="@android:style/Theme.DeviceDefault.DayNight">
         <item name="android:windowBackground">@android:color/transparent</item>
+        <item name="android:windowNoTitle">true</item>
+        <item name="android:windowContentOverlay">@null</item>
+        <item name="android:colorBackgroundCacheHint">@null</item>
+        <item name="android:windowIsTranslucent">true</item>
+        <item name="android:windowAnimationStyle">@android:style/Animation</item>
+
+        <item name="widgetsTheme">@style/WidgetContainerTheme</item>
         <item name="pageIndicatorDotColor">@color/page_indicator_dot_color_light</item>
     </style>
 </resources>
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 5e5487b..44601b7 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -356,6 +356,14 @@
         options.setOnAnimationAbortListener(endCallback);
         options.setOnAnimationFinishedListener(endCallback);
 
+        // Prepare taskbar for animation synchronization. This needs to happen here before any
+        // app transition is created.
+        LauncherTaskbarUIController taskbarController = mLauncher.getTaskbarUIController();
+        if (enableScalingRevealHomeAnimation() && taskbarController != null) {
+            taskbarController.setIgnoreInAppFlagForSync(true);
+            onEndCallback.add(() -> taskbarController.setIgnoreInAppFlagForSync(false));
+        }
+
         IBinder cookie = mAppLaunchRunner.supportsReturnTransition()
                 ? ((ContainerAnimationRunner) mAppLaunchRunner).getCookie() : null;
         addLaunchCookie(cookie, (ItemInfo) v.getTag(), options);
@@ -1924,6 +1932,21 @@
                 anim.addListener(mForceInvisibleListener);
             }
 
+            // Syncs the app launch animation and taskbar stash animation (if exists).
+            if (enableScalingRevealHomeAnimation()) {
+                LauncherTaskbarUIController taskbarController = mLauncher.getTaskbarUIController();
+                if (taskbarController != null) {
+                    taskbarController.setIgnoreInAppFlagForSync(false);
+
+                    if (launcherClosing) {
+                        Animator taskbar = taskbarController.createAnimToApp();
+                        if (taskbar != null) {
+                            anim.play(taskbar);
+                        }
+                    }
+                }
+            }
+
             result.setAnimation(anim, mLauncher, mOnEndCallback::executeAllAndDestroy,
                     skipFirstFrame);
         }
diff --git a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
index 44d8a5c..0b18633 100644
--- a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
+++ b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
@@ -28,6 +28,7 @@
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.ClipData;
 import android.content.ClipDescription;
+import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
 import android.util.Log;
@@ -46,11 +47,11 @@
 import com.android.launcher3.model.WidgetPredictionsRequester;
 import com.android.launcher3.model.WidgetsModel;
 import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.PackageItemInfo;
 import com.android.launcher3.popup.PopupDataProvider;
-import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.widget.WidgetCell;
+import com.android.launcher3.widget.model.WidgetsListBaseEntriesBuilder;
 import com.android.launcher3.widget.model.WidgetsListBaseEntry;
-import com.android.launcher3.widget.model.WidgetsListContentEntry;
 import com.android.launcher3.widget.picker.WidgetsFullSheet;
 
 import java.util.ArrayList;
@@ -59,9 +60,8 @@
 import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
-import java.util.function.Function;
+import java.util.function.Predicate;
 import java.util.regex.Pattern;
-import java.util.stream.Collectors;
 
 /** An Activity that can host Launcher's widget picker. */
 public class WidgetPickerActivity extends BaseActivity {
@@ -110,7 +110,8 @@
     private WidgetsModel mModel;
     private LauncherAppState mApp;
     private WidgetPredictionsRequester mWidgetPredictionsRequester;
-    private final PopupDataProvider mPopupDataProvider = new PopupDataProvider(i -> {});
+    private final PopupDataProvider mPopupDataProvider = new PopupDataProvider(i -> {
+    });
 
     private int mDesiredWidgetWidth;
     private int mDesiredWidgetHeight;
@@ -128,9 +129,23 @@
     /** A set of user ids that should be filtered out from the selected widgets. */
     @NonNull
     Set<Integer> mFilteredUserIds = new HashSet<>();
+
     @Nullable
     private WidgetsFullSheet mWidgetSheet;
 
+    private final Predicate<WidgetItem> mWidgetsFilter = widget -> {
+        final WidgetAcceptabilityVerdict verdict =
+                isWidgetAcceptable(widget, /* applySizeFilter=*/ false);
+        verdict.maybeLogVerdict();
+        return verdict.isAcceptable;
+    };
+    private final Predicate<WidgetItem> mDefaultWidgetsFilter = widget -> {
+        final WidgetAcceptabilityVerdict verdict =
+                isWidgetAcceptable(widget, /* applySizeFilter=*/ true);
+        verdict.maybeLogVerdict();
+        return verdict.isAcceptable;
+    };
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -271,47 +286,41 @@
         };
     }
 
-    /** Updates the model with widgets, applies filters and launches the widgets sheet once
-     * widgets are available */
+    /**
+     * Updates the model with widgets, applies filters and launches the widgets sheet once
+     * widgets are available
+     */
     private void refreshAndBindWidgets() {
         MODEL_EXECUTOR.execute(() -> {
             LauncherAppState app = LauncherAppState.getInstance(this);
+            Context context = app.getContext();
+
             mModel.update(app, null);
-            final List<WidgetsListBaseEntry> allWidgets =
-                    mModel.getFilteredWidgetsListForPicker(
-                            app.getContext(),
-                            /*widgetItemFilter=*/ widget -> {
-                                final WidgetAcceptabilityVerdict verdict =
-                                        isWidgetAcceptable(widget);
-                                verdict.maybeLogVerdict();
-                                return verdict.isAcceptable;
-                            }
-                    );
-            bindWidgets(allWidgets);
+            bindWidgets(mModel.getWidgetsByPackageItem());
             // Open sheet once widgets are available, so that it doesn't interrupt the open
             // animation.
             openWidgetsSheet();
             if (mUiSurface != null) {
-                Map<ComponentKey, WidgetItem> allWidgetItems = allWidgets.stream()
-                        .filter(entry -> entry instanceof WidgetsListContentEntry)
-                        .flatMap(entry -> entry.mWidgets.stream())
-                        .distinct()
-                        .collect(Collectors.toMap(
-                                widget -> new ComponentKey(widget.componentName, widget.user),
-                                Function.identity()
-                        ));
                 mWidgetPredictionsRequester = new WidgetPredictionsRequester(app.getContext(),
-                        mUiSurface, allWidgetItems);
+                        mUiSurface, mModel.getWidgetsByComponentKey());
                 mWidgetPredictionsRequester.request(mAddedWidgets, this::bindRecommendedWidgets);
             }
         });
     }
 
-    private void bindWidgets(List<WidgetsListBaseEntry> widgets) {
-        MAIN_EXECUTOR.execute(() -> mPopupDataProvider.setAllWidgets(widgets));
+    private void bindWidgets(Map<PackageItemInfo, List<WidgetItem>> widgets) {
+        WidgetsListBaseEntriesBuilder builder = new WidgetsListBaseEntriesBuilder(
+                mApp.getContext());
+
+        final List<WidgetsListBaseEntry> allWidgets = builder.build(widgets, mWidgetsFilter);
+        final List<WidgetsListBaseEntry> defaultWidgets =
+                shouldShowDefaultWidgets() ? builder.build(widgets,
+                        mDefaultWidgetsFilter) : List.of();
+
+        MAIN_EXECUTOR.execute(() -> mPopupDataProvider.setAllWidgets(allWidgets, defaultWidgets));
     }
 
-   private void openWidgetsSheet() {
+    private void openWidgetsSheet() {
         MAIN_EXECUTOR.execute(() -> {
             mWidgetSheet = WidgetsFullSheet.show(this, true);
             mWidgetSheet.mayUpdateTitleAndDescription(mTitle, mDescription);
@@ -378,9 +387,15 @@
             mActiveOnBackAnimationCallback.onBackCancelled();
             mActiveOnBackAnimationCallback = null;
         }
-    };
+    }
 
-    private WidgetAcceptabilityVerdict isWidgetAcceptable(WidgetItem widget) {
+    private boolean shouldShowDefaultWidgets() {
+        // If optional filters such as size filter are present, we display them as default widgets.
+        return mDesiredWidgetWidth != 0 || mDesiredWidgetHeight != 0;
+    }
+
+    private WidgetAcceptabilityVerdict isWidgetAcceptable(WidgetItem widget,
+            boolean applySizeFilter) {
         final AppWidgetProviderInfo info = widget.widgetInfo;
         if (info == null) {
             return rejectWidget(widget, "shortcut");
@@ -401,61 +416,63 @@
                     info.widgetCategory);
         }
 
-        if (mDesiredWidgetWidth == 0 && mDesiredWidgetHeight == 0) {
-            // Accept the widget if the desired dimensions are unspecified.
-            return acceptWidget(widget);
-        }
-
-        final boolean isHorizontallyResizable =
-                (info.resizeMode & AppWidgetProviderInfo.RESIZE_HORIZONTAL) != 0;
-        if (mDesiredWidgetWidth > 0 && isHorizontallyResizable) {
-            if (info.maxResizeWidth > 0
-                    && info.maxResizeWidth >= info.minWidth
-                    && info.maxResizeWidth < mDesiredWidgetWidth) {
-                return rejectWidget(
-                        widget,
-                        "maxResizeWidth[%d] < mDesiredWidgetWidth[%d]",
-                        info.maxResizeWidth,
-                        mDesiredWidgetWidth);
+        if (applySizeFilter) {
+            if (mDesiredWidgetWidth == 0 && mDesiredWidgetHeight == 0) {
+                // Accept the widget if the desired dimensions are unspecified.
+                return acceptWidget(widget);
             }
 
-            final int minWidth = Math.min(info.minResizeWidth, info.minWidth);
-            if (minWidth > mDesiredWidgetWidth) {
-                return rejectWidget(
-                        widget,
-                        "min(minWidth[%d], minResizeWidth[%d]) > mDesiredWidgetWidth[%d]",
-                        info.minWidth,
-                        info.minResizeWidth,
-                        mDesiredWidgetWidth);
-            }
-        }
+            final boolean isHorizontallyResizable =
+                    (info.resizeMode & AppWidgetProviderInfo.RESIZE_HORIZONTAL) != 0;
+            if (mDesiredWidgetWidth > 0 && isHorizontallyResizable) {
+                if (info.maxResizeWidth > 0
+                        && info.maxResizeWidth >= info.minWidth
+                        && info.maxResizeWidth < mDesiredWidgetWidth) {
+                    return rejectWidget(
+                            widget,
+                            "maxResizeWidth[%d] < mDesiredWidgetWidth[%d]",
+                            info.maxResizeWidth,
+                            mDesiredWidgetWidth);
+                }
 
-        final boolean isVerticallyResizable =
-                (info.resizeMode & AppWidgetProviderInfo.RESIZE_VERTICAL) != 0;
-        if (mDesiredWidgetHeight > 0 && isVerticallyResizable) {
-            if (info.maxResizeHeight > 0
-                    && info.maxResizeHeight >= info.minHeight
-                    && info.maxResizeHeight < mDesiredWidgetHeight) {
-                return rejectWidget(
-                        widget,
-                        "maxResizeHeight[%d] < mDesiredWidgetHeight[%d]",
-                        info.maxResizeHeight,
-                        mDesiredWidgetHeight);
+                final int minWidth = Math.min(info.minResizeWidth, info.minWidth);
+                if (minWidth > mDesiredWidgetWidth) {
+                    return rejectWidget(
+                            widget,
+                            "min(minWidth[%d], minResizeWidth[%d]) > mDesiredWidgetWidth[%d]",
+                            info.minWidth,
+                            info.minResizeWidth,
+                            mDesiredWidgetWidth);
+                }
             }
 
-            final int minHeight = Math.min(info.minResizeHeight, info.minHeight);
-            if (minHeight > mDesiredWidgetHeight) {
-                return rejectWidget(
-                        widget,
-                        "min(minHeight[%d], minResizeHeight[%d]) > mDesiredWidgetHeight[%d]",
-                        info.minHeight,
-                        info.minResizeHeight,
-                        mDesiredWidgetHeight);
-            }
-        }
+            final boolean isVerticallyResizable =
+                    (info.resizeMode & AppWidgetProviderInfo.RESIZE_VERTICAL) != 0;
+            if (mDesiredWidgetHeight > 0 && isVerticallyResizable) {
+                if (info.maxResizeHeight > 0
+                        && info.maxResizeHeight >= info.minHeight
+                        && info.maxResizeHeight < mDesiredWidgetHeight) {
+                    return rejectWidget(
+                            widget,
+                            "maxResizeHeight[%d] < mDesiredWidgetHeight[%d]",
+                            info.maxResizeHeight,
+                            mDesiredWidgetHeight);
+                }
 
-        if (!isHorizontallyResizable || !isVerticallyResizable) {
-            return rejectWidget(widget, "not resizeable");
+                final int minHeight = Math.min(info.minResizeHeight, info.minHeight);
+                if (minHeight > mDesiredWidgetHeight) {
+                    return rejectWidget(
+                            widget,
+                            "min(minHeight[%d], minResizeHeight[%d]) > mDesiredWidgetHeight[%d]",
+                            info.minHeight,
+                            info.minResizeHeight,
+                            mDesiredWidgetHeight);
+                }
+            }
+
+            if (!isHorizontallyResizable || !isVerticallyResizable) {
+                return rejectWidget(widget, "not resizeable");
+            }
         }
 
         return acceptWidget(widget);
diff --git a/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java b/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
index 84c2ed2..7a8b58e 100644
--- a/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
+++ b/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
@@ -31,12 +31,12 @@
 import android.view.accessibility.AccessibilityManager;
 
 import androidx.annotation.ColorInt;
-import androidx.core.content.ContextCompat;
 
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.allapps.FloatingHeaderRow;
 import com.android.launcher3.allapps.FloatingHeaderView;
+import com.android.launcher3.util.Themes;
 
 /**
  * A view which shows a horizontal divider
@@ -84,10 +84,9 @@
                 getResources().getDimensionPixelSize(R.dimen.all_apps_divider_height)
         };
 
-        mStrokeColor = ContextCompat.getColor(context, R.color.material_color_outline_variant);
+        mStrokeColor = Themes.getAttrColor(context, R.attr.materialColorOutlineVariant);
 
-        mAllAppsLabelTextColor = ContextCompat.getColor(context,
-                R.color.material_color_on_surface_variant);
+        mAllAppsLabelTextColor = Themes.getAttrColor(context, R.attr.materialColorOnSurfaceVariant);
 
         mAccessibilityManager = AccessibilityManager.getInstance(context);
         setShowAllAppsLabel(!ALL_APPS_VISITED_COUNT.hasReachedMax(context));
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 779009a..0add1c4 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -19,6 +19,7 @@
 import static com.android.launcher3.statemanager.BaseState.FLAG_NON_INTERACTIVE;
 import static com.android.launcher3.taskbar.TaskbarEduTooltipControllerKt.TOOLTIP_STEP_FEATURES;
 import static com.android.launcher3.taskbar.TaskbarLauncherStateController.FLAG_VISIBLE;
+import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IGNORE_IN_APP;
 import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
 import static com.android.window.flags.Flags.enableDesktopWindowingWallpaperActivity;
 
@@ -256,6 +257,24 @@
         return mTaskbarLauncherStateController.createAnimToLauncher(toState, callbacks, duration);
     }
 
+    /**
+     * Create Taskbar animation to be played alongside the Launcher app launch animation.
+     */
+    public @Nullable Animator createAnimToApp() {
+        TaskbarStashController stashController = mControllers.taskbarStashController;
+        stashController.updateStateForFlag(TaskbarStashController.FLAG_IN_APP, true);
+        return stashController.createApplyStateAnimator(stashController.getStashDuration());
+    }
+
+    /**
+     * Temporarily ignore FLAG_IN_APP for app launches to prevent premature taskbar stashing.
+     * This is needed because taskbar gets a signal to stash before we actually start the
+     * app launch animation.
+     */
+    public void setIgnoreInAppFlagForSync(boolean enabled) {
+        mControllers.taskbarStashController.updateStateForFlag(FLAG_IGNORE_IN_APP, enabled);
+    }
+
     public void updateTaskbarLauncherStateGoingHome() {
         mTaskbarLauncherStateController.updateStateForFlag(FLAG_VISIBLE, true);
         mTaskbarLauncherStateController.applyState();
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltip.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltip.kt
index 7f9d8a3..19e9872 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltip.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltip.kt
@@ -53,8 +53,7 @@
 
     private val activityContext: ActivityContext = ActivityContext.lookupContext(context)
 
-    private val backgroundColor =
-        Themes.getAttrColor(context, com.android.internal.R.attr.materialColorSurfaceBright)
+    private val backgroundColor = Themes.getAttrColor(context, R.attr.materialColorSurfaceBright)
 
     private val tooltipCornerRadius = Themes.getDialogCornerRadius(context)
     private val arrowWidth = resources.getDimension(R.dimen.popup_arrow_width)
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index 430c003..0c5ad42 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -95,6 +95,7 @@
     public static final int FLAG_STASHED_SYSUI = 1 << 9; //  app pinning,...
     public static final int FLAG_STASHED_DEVICE_LOCKED = 1 << 10; // device is locked: keyguard, ...
     public static final int FLAG_IN_OVERVIEW = 1 << 11; // launcher is in overview
+    public static final int FLAG_IGNORE_IN_APP = 1 << 12; // used to sync with app launch animation
 
     // If any of these flags are enabled, isInApp should return true.
     private static final int FLAGS_IN_APP = FLAG_IN_APP | FLAG_IN_SETUP;
@@ -1263,6 +1264,11 @@
          */
         @Nullable
         public Animator createSetStateAnimator(long flags, long duration) {
+            // We do this when we want to synchronize the app launch and taskbar stash animations.
+            if (hasAnyFlag(FLAG_IGNORE_IN_APP) && hasAnyFlag(flags, FLAG_IN_APP)) {
+                flags = flags & ~FLAG_IN_APP;
+            }
+
             boolean isStashed = mStashCondition.test(flags);
 
             if (DEBUG) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
index 7426dc7..5be0171 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
@@ -33,7 +33,6 @@
 
 import android.annotation.BinderThread;
 import android.annotation.Nullable;
-import android.app.Notification;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.LauncherApps;
@@ -463,15 +462,6 @@
     /** Tells WMShell to show the currently selected bubble. */
     public void showSelectedBubble() {
         if (getSelectedBubbleKey() != null) {
-            if (mSelectedBubble instanceof BubbleBarBubble) {
-                // Because we've visited this bubble, we should suppress the notification.
-                // This is updated on WMShell side when we show the bubble, but that update isn't
-                // passed to launcher, instead we apply it directly here.
-                BubbleInfo info = ((BubbleBarBubble) mSelectedBubble).getInfo();
-                info.setFlags(
-                        info.getFlags() | Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION);
-                mSelectedBubble.getView().updateDotVisibility(true /* animate */);
-            }
             mLastSentBubbleBarTop = mBarView.getRestingTopPositionOnScreen();
             mSystemUiProxy.showBubble(getSelectedBubbleKey(), mLastSentBubbleBarTop);
         } else {
@@ -645,8 +635,8 @@
 
         final TypedArray ta = mContext.obtainStyledAttributes(
                 new int[]{
-                        com.android.internal.R.attr.materialColorOnPrimaryFixed,
-                        com.android.internal.R.attr.materialColorPrimaryFixed
+                        R.attr.materialColorOnPrimaryFixed,
+                        R.attr.materialColorPrimaryFixed
                 });
         int overflowIconColor = ta.getColor(0, Color.WHITE);
         int overflowBackgroundColor = ta.getColor(1, Color.BLACK);
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
index fd989b1..4794dfd 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
@@ -240,6 +240,10 @@
                         BubbleView firstBubble = (BubbleView) getChildAt(0);
                         mUpdateSelectedBubbleAfterCollapse.accept(firstBubble.getBubble().getKey());
                     }
+                    // If the bar was just expanded, remove the dot from the selected bubble.
+                    if (mIsBarExpanded && mSelectedBubbleView != null) {
+                        mSelectedBubbleView.markSeen();
+                    }
                     updateWidth();
                 },
                 /* onUpdate= */ animator -> {
@@ -665,7 +669,7 @@
     }
 
     /** Add a new bubble to the bubble bar. */
-    public void addBubble(View bubble) {
+    public void addBubble(BubbleView bubble) {
         FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams((int) mIconSize, (int) mIconSize,
                 Gravity.LEFT);
         if (isExpanded()) {
@@ -673,6 +677,7 @@
             bubble.setScaleX(0f);
             bubble.setScaleY(0f);
             addView(bubble, 0, lp);
+            bubble.showDotIfNeeded(/* animate= */ false);
 
             mBubbleAnimator = new BubbleAnimator(mIconSize, mExpandedBarIconsSpacing,
                     getChildCount(), mBubbleBarLocation.isOnLeft(isLayoutRtl()));
@@ -825,6 +830,23 @@
         updateBubbleAccessibilityStates();
         updateContentDescription();
         mDismissedByDragBubbleView = null;
+        updateNotificationDotsIfCollapsed();
+    }
+
+    private void updateNotificationDotsIfCollapsed() {
+        if (isExpanded()) {
+            return;
+        }
+        for (int i = 0; i < getChildCount(); i++) {
+            BubbleView bubbleView = (BubbleView) getChildAt(i);
+            // when we're collapsed, the first bubble should show the dot if it has it. the rest of
+            // the bubbles should hide their dots.
+            if (i == 0 && bubbleView.hasUnseenContent()) {
+                bubbleView.showDotIfNeeded(/* animate= */ true);
+            } else {
+                bubbleView.hideDot();
+            }
+        }
     }
 
     private void updateWidth() {
@@ -865,7 +887,6 @@
         float bubbleBarAnimatedTop = viewBottom - getBubbleBarHeight();
         // When translating X & Y the scale is ignored, so need to deduct it from the translations
         final float ty = bubbleBarAnimatedTop + mBubbleBarPadding - getScaleIconShift();
-        final boolean animate = getVisibility() == VISIBLE;
         final boolean onLeft = bubbleBarLocation.isOnLeft(isLayoutRtl());
         // elevation state is opposite to widthState - when expanded all icons are flat
         float elevationState = (1 - widthState);
@@ -897,10 +918,10 @@
             bv.setZ(fullElevationForChild * elevationState);
 
             // only update the dot scale if we're expanding or collapsing
-            // TODO b/351904597: update the dot for the first bubble after removal and reorder
-            // since those might happen when the bar is collapsed and will need their dot back
             if (mWidthAnimator.isRunning()) {
-                bv.setDotScale(widthState);
+                // The dot for the selected bubble scales in the opposite direction of the expansion
+                // animation.
+                bv.showDotIfNeeded(bv == mSelectedBubbleView ? 1 - widthState : widthState);
             }
 
             if (mIsBarExpanded) {
@@ -1025,6 +1046,7 @@
             }
             updateBubblesLayoutProperties(mBubbleBarLocation);
             updateContentDescription();
+            updateNotificationDotsIfCollapsed();
         }
     }
 
@@ -1049,6 +1071,14 @@
         if (mBubbleAnimator == null) {
             updateArrowForSelected(previouslySelectedBubble != null);
         }
+        if (view != null) {
+            if (isExpanded()) {
+                view.markSeen();
+            } else {
+                // when collapsed, the selected bubble should show the dot if it has it
+                view.showDotIfNeeded(/* animate= */ true);
+            }
+        }
     }
 
     /**
@@ -1316,6 +1346,7 @@
     public void dump(PrintWriter pw) {
         pw.println("BubbleBarView state:");
         pw.println("  visibility: " + getVisibility());
+        pw.println("  alpha: " + getAlpha());
         pw.println("  translation Y: " + getTranslationY());
         pw.println("  bubbles in bar (childCount = " + getChildCount() + ")");
         for (BubbleView bubbleView: getBubbles()) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
index 24b9139..2311d42 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
@@ -31,6 +31,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.AnimatedFloat;
 import com.android.launcher3.taskbar.TaskbarActivityContext;
@@ -62,6 +63,7 @@
     private final TaskbarActivityContext mActivity;
     private final BubbleBarView mBarView;
     private int mIconSize;
+    private int mBubbleBarPadding;
 
     // Initialized in init.
     private BubbleStashController mBubbleStashController;
@@ -110,12 +112,11 @@
         mTaskbarStashController = controllers.taskbarStashController;
         mTaskbarInsetsController = controllers.taskbarInsetsController;
         mBubbleBarViewAnimator = new BubbleBarViewAnimator(mBarView, mBubbleStashController);
-
+        onBubbleBarConfigurationChanged(/* animate= */ false);
         mActivity.addOnDeviceProfileChangeListener(
-                dp -> updateBubbleBarIconSize(dp.taskbarIconSize, /* animate= */ true));
-        updateBubbleBarIconSize(mActivity.getDeviceProfile().taskbarIconSize, /* animate= */ false);
+                dp -> onBubbleBarConfigurationChanged(/* animate= */ true));
         mBubbleBarScale.updateValue(1f);
-        mBubbleClickListener = v -> onBubbleClicked(v);
+        mBubbleClickListener = v -> onBubbleClicked((BubbleView) v);
         mBubbleBarClickListener = v -> onBubbleBarClicked();
         mBubbleDragController.setupBubbleBarView(mBarView);
         mBarView.setOnClickListener(mBubbleBarClickListener);
@@ -139,8 +140,9 @@
         });
     }
 
-    private void onBubbleClicked(View v) {
-        BubbleBarItem bubble = ((BubbleView) v).getBubble();
+    private void onBubbleClicked(BubbleView bubbleView) {
+        bubbleView.markSeen();
+        BubbleBarItem bubble = bubbleView.getBubble();
         if (bubble == null) {
             Log.e(TAG, "bubble click listener, bubble was null");
         }
@@ -334,27 +336,60 @@
     // Modifying view related properties.
     //
 
-    private void updateBubbleBarIconSize(int newIconSize, boolean animate) {
+    /** Notifies controller of configuration change, so bubble bar can be adjusted */
+    public void onBubbleBarConfigurationChanged(boolean animate) {
+        int newIconSize;
+        int newPadding;
         Resources res = mActivity.getResources();
+        if (mBubbleStashController.isBubblesShowingOnHome()) {
+            newIconSize = getBubbleBarIconSizeFromDeviceProfile(res);
+            newPadding = getBubbleBarPaddingFromDeviceProfile(res);
+        } else {
+            // the bubble bar is shown inside the persistent task bar, use preset sizes
+            newIconSize = res.getDimensionPixelSize(R.dimen.bubblebar_icon_size_persistent_taskbar);
+            newPadding = res.getDimensionPixelSize(
+                    R.dimen.bubblebar_icon_spacing_persistent_taskbar);
+        }
+        updateBubbleBarIconSizeAndPadding(newIconSize, newPadding, animate);
+    }
+
+
+    private int getBubbleBarIconSizeFromDeviceProfile(Resources res) {
+        DeviceProfile deviceProfile = mActivity.getDeviceProfile();
         DisplayMetrics dm = res.getDisplayMetrics();
         float smallIconSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                 APP_ICON_SMALL_DP, dm);
         float mediumIconSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                 APP_ICON_MEDIUM_DP, dm);
-        float largeIconSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
-                APP_ICON_LARGE_DP, dm);
         float smallMediumThreshold = (smallIconSize + mediumIconSize) / 2f;
-        float mediumLargeThreshold = (mediumIconSize + largeIconSize) / 2f;
-        mIconSize = newIconSize <= smallMediumThreshold
+        int taskbarIconSize = deviceProfile.taskbarIconSize;
+        return taskbarIconSize <= smallMediumThreshold
                 ? res.getDimensionPixelSize(R.dimen.bubblebar_icon_size_small) :
                 res.getDimensionPixelSize(R.dimen.bubblebar_icon_size);
-        float bubbleBarPadding = newIconSize >= mediumLargeThreshold
+
+    }
+
+    private int getBubbleBarPaddingFromDeviceProfile(Resources res) {
+        DeviceProfile deviceProfile = mActivity.getDeviceProfile();
+        DisplayMetrics dm = res.getDisplayMetrics();
+        float mediumIconSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+                APP_ICON_MEDIUM_DP, dm);
+        float largeIconSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+                APP_ICON_LARGE_DP, dm);
+        float mediumLargeThreshold = (mediumIconSize + largeIconSize) / 2f;
+        return deviceProfile.taskbarIconSize >= mediumLargeThreshold
                 ? res.getDimensionPixelSize(R.dimen.bubblebar_icon_spacing_large) :
                 res.getDimensionPixelSize(R.dimen.bubblebar_icon_spacing);
+    }
+
+    private void updateBubbleBarIconSizeAndPadding(int iconSize, int padding, boolean animate) {
+        if (mIconSize == iconSize && mBubbleBarPadding == padding) return;
+        mIconSize = iconSize;
+        mBubbleBarPadding = padding;
         if (animate) {
-            mBarView.animateBubbleBarIconSize(mIconSize, bubbleBarPadding);
+            mBarView.animateBubbleBarIconSize(iconSize, padding);
         } else {
-            mBarView.setIconSizeAndPadding(mIconSize, bubbleBarPadding);
+            mBarView.setIconSizeAndPadding(iconSize, padding);
         }
     }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
index 4c468bb..acb6b4e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
@@ -16,6 +16,7 @@
 package com.android.launcher3.taskbar.bubbles;
 
 import android.annotation.Nullable;
+import android.app.Notification;
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
@@ -35,6 +36,7 @@
 import com.android.launcher3.icons.DotRenderer;
 import com.android.launcher3.icons.IconNormalizer;
 import com.android.wm.shell.animation.Interpolators;
+import com.android.wm.shell.common.bubbles.BubbleInfo;
 
 // TODO: (b/276978250) This is will be similar to WMShell's BadgedImageView, it'd be nice to share.
 
@@ -217,9 +219,9 @@
     }
 
     void updateDotVisibility(boolean animate) {
-        final float targetScale = shouldDrawDot() ? 1f : 0f;
+        final float targetScale = hasUnseenContent() ? 1f : 0f;
         if (animate) {
-            animateDotScale();
+            animateDotScale(targetScale);
         } else {
             mDotScale = targetScale;
             mAnimatingToDotScale = targetScale;
@@ -241,18 +243,27 @@
         mAppIcon.setVisibility(show ? VISIBLE : GONE);
     }
 
-    /** Whether the dot indicating unseen content in a bubble should be shown. */
-    private boolean shouldDrawDot() {
-        boolean bubbleHasUnseenContent = mBubble != null
+    boolean hasUnseenContent() {
+        return mBubble != null
                 && mBubble instanceof BubbleBarBubble
                 && !((BubbleBarBubble) mBubble).getInfo().isNotificationSuppressed();
-        // Always render the dot if it's animating, since it could be animating out. Otherwise, show
-        // it if the bubble wants to show it, and we aren't suppressing it.
-        return bubbleHasUnseenContent || mDotIsAnimating;
     }
 
-    /** How big the dot should be, fraction from 0 to 1. */
-    void setDotScale(float fraction) {
+    /**
+     * Used to determine if we can skip drawing frames.
+     *
+     * <p>Generally we should draw the dot when it is requested to be shown and there is unseen
+     * content. But when the dot is removed, we still want to draw frames so that it can be scaled
+     * out.
+     */
+    private boolean shouldDrawDot() {
+        // if there's no dot there's nothing to draw, unless the dot was removed and we're in the
+        // middle of removing it
+        return hasUnseenContent() || mDotIsAnimating;
+    }
+
+    /** Updates the dot scale to the specified fraction from 0 to 1. */
+    private void setDotScale(float fraction) {
         if (!shouldDrawDot()) {
             return;
         }
@@ -260,11 +271,41 @@
         invalidate();
     }
 
-    /**
-     * Animates the dot to the given scale.
-     */
-    private void animateDotScale() {
-        float toScale = shouldDrawDot() ? 1f : 0f;
+    void showDotIfNeeded(float fraction) {
+        if (!hasUnseenContent()) {
+            return;
+        }
+        setDotScale(fraction);
+    }
+
+    void showDotIfNeeded(boolean animate) {
+        // only show the dot if we have unseen content
+        if (!hasUnseenContent()) {
+            return;
+        }
+        if (animate) {
+            animateDotScale(1f);
+        } else {
+            setDotScale(1f);
+        }
+    }
+
+    void hideDot() {
+        animateDotScale(0f);
+    }
+
+    /** Marks this bubble such that it no longer has unseen content, and hides the dot. */
+    void markSeen() {
+        if (mBubble instanceof BubbleBarBubble bubble) {
+            BubbleInfo info = bubble.getInfo();
+            info.setFlags(
+                    info.getFlags() | Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION);
+            hideDot();
+        }
+    }
+
+    /** Animates the dot to the given scale. */
+    private void animateDotScale(float toScale) {
         boolean isDotScaleChanging = Float.compare(mDotScale, toScale) != 0;
 
         // Don't restart the animation if we're already animating to the given value or if the dot
@@ -277,8 +318,6 @@
 
         final boolean showDot = toScale > 0f;
 
-        // Do NOT wait until after animation ends to setShowDot
-        // to avoid overriding more recent showDot states.
         clearAnimation();
         animate()
                 .setDuration(200)
@@ -293,7 +332,6 @@
                 }).start();
     }
 
-
     @Override
     public String toString() {
         String toString = mBubble != null ? mBubble.getKey() : "null";
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index f020c8f..20eaddc 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -1631,14 +1631,17 @@
                             mRecentsAnimationController.screenshotTask(taskId));
                 });
 
-                // let SystemUi reparent the overlay leash as soon as possible
+                // let SystemUi reparent the overlay leash as soon as possible;
+                // make sure to pass in an empty src-rect-hint if overlay is present, since we
+                // use our own calculated source-rect-hint for the animation.
                 SystemUiProxy.INSTANCE.get(mContext).stopSwipePipToHome(
                         mSwipePipToHomeAnimator.getTaskId(),
                         mSwipePipToHomeAnimator.getComponentName(),
                         mSwipePipToHomeAnimator.getDestinationBounds(),
                         mSwipePipToHomeAnimator.getContentOverlay(),
                         mSwipePipToHomeAnimator.getAppBounds(),
-                        mSwipePipToHomeAnimator.getSourceRectHint());
+                        mSwipePipToHomeAnimator.getContentOverlay() != null ? new Rect()
+                                : mSwipePipToHomeAnimator.getSourceRectHint());
 
                 windowAnim = mSwipePipToHomeAnimators;
             } else {
diff --git a/quickstep/src/com/android/quickstep/LauncherRestoreEventLoggerImpl.kt b/quickstep/src/com/android/quickstep/LauncherRestoreEventLoggerImpl.kt
index 27bd03d..7d22c52 100644
--- a/quickstep/src/com/android/quickstep/LauncherRestoreEventLoggerImpl.kt
+++ b/quickstep/src/com/android/quickstep/LauncherRestoreEventLoggerImpl.kt
@@ -5,6 +5,7 @@
 import android.app.backup.BackupRestoreEventLogger.BackupRestoreDataType
 import android.app.backup.BackupRestoreEventLogger.BackupRestoreError
 import android.content.Context
+import androidx.annotation.VisibleForTesting
 import com.android.launcher3.Flags.enableLauncherBrMetricsFixed
 import com.android.launcher3.LauncherSettings.Favorites
 import com.android.launcher3.backuprestore.LauncherRestoreEventLogger
@@ -29,8 +30,8 @@
         @BackupRestoreDataType private const val DATA_TYPE_APP_PAIR = "app_pair"
     }
 
-    private val restoreEventLogger: BackupRestoreEventLogger =
-        BackupManager(context).delayedRestoreLogger
+    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+    val restoreEventLogger: BackupRestoreEventLogger = BackupManager(context).delayedRestoreLogger
 
     /**
      * For logging when multiple items of a given data type failed to restore.
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index 3d4167a..4989831 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -300,8 +300,12 @@
     @VisibleForTesting
     TaskLoadResult loadTasksInBackground(int numTasks, int requestId, boolean loadKeysOnly) {
         int currentUserId = Process.myUserHandle().getIdentifier();
-        ArrayList<GroupedRecentTaskInfo> rawTasks =
-                mSysUiProxy.getRecentTasks(numTasks, currentUserId);
+        ArrayList<GroupedRecentTaskInfo> rawTasks;
+        try {
+            rawTasks = mSysUiProxy.getRecentTasks(numTasks, currentUserId);
+        } catch (SystemUiProxy.GetRecentTasksException e) {
+            return INVALID_RESULT;
+        }
         // The raw tasks are given in most-recent to least-recent order, we need to reverse it
         Collections.reverse(rawTasks);
 
@@ -416,8 +420,12 @@
         }
         writer.println(prefix + "  ]");
         int currentUserId = Process.myUserHandle().getIdentifier();
-        ArrayList<GroupedRecentTaskInfo> rawTasks =
-                mSysUiProxy.getRecentTasks(Integer.MAX_VALUE, currentUserId);
+        ArrayList<GroupedRecentTaskInfo> rawTasks;
+        try {
+            rawTasks = mSysUiProxy.getRecentTasks(Integer.MAX_VALUE, currentUserId);
+        } catch (SystemUiProxy.GetRecentTasksException e) {
+            rawTasks = new ArrayList<>();
+        }
         writer.println(prefix + "  rawTasks=[");
         for (GroupedRecentTaskInfo task : rawTasks) {
             TaskInfo taskInfo1 = task.getTaskInfo1();
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index b796951..f2b6005 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -18,6 +18,7 @@
 import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
 
 import static com.android.launcher3.Flags.enableGridOnlyOverview;
+import static com.android.launcher3.Flags.enableRefactorTaskThumbnail;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
 
@@ -108,7 +109,7 @@
         mIconCache = iconCache;
         mIconCache.registerTaskVisualsChangeListener(this);
         mThumbnailCache = thumbnailCache;
-        if (enableGridOnlyOverview()) {
+        if (isCachePreloadingEnabled()) {
             mCallbacks = new ComponentCallbacks() {
                 @Override
                 public void onConfigurationChanged(Configuration configuration) {
@@ -342,7 +343,7 @@
      * highResLoadingState is enabled
      */
     public void preloadCacheIfNeeded() {
-        if (!enableGridOnlyOverview()) {
+        if (!isCachePreloadingEnabled()) {
             return;
         }
 
@@ -368,7 +369,7 @@
      * Updates cache size and preloads more tasks if cache size increases
      */
     public void updateCacheSizeAndPreloadIfNeeded() {
-        if (!enableGridOnlyOverview()) {
+        if (!isCachePreloadingEnabled()) {
             return;
         }
 
@@ -387,6 +388,10 @@
         mTaskStackChangeListeners.unregisterTaskStackListener(this);
     }
 
+    private boolean isCachePreloadingEnabled() {
+        return enableGridOnlyOverview() || enableRefactorTaskThumbnail();
+    }
+
     /**
      * Listener for receiving running tasks changes
      */
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 66aa897..3f73959 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -1394,10 +1394,26 @@
         }
     }
 
-    public ArrayList<GroupedRecentTaskInfo> getRecentTasks(int numTasks, int userId) {
+    public static class GetRecentTasksException extends Exception {
+        public GetRecentTasksException(String message) {
+            super(message);
+        }
+
+        public GetRecentTasksException(String message, Throwable cause) {
+            super(message, cause);
+        }
+    }
+
+    /**
+     * Retrieves a list of Recent tasks from ActivityManager.
+     * @throws GetRecentTasksException if IRecentTasks is not initialized, or when we get
+     * RemoteException from server side
+     */
+    public ArrayList<GroupedRecentTaskInfo> getRecentTasks(int numTasks, int userId)
+            throws GetRecentTasksException {
         if (mRecentTasks == null) {
-            Log.w(TAG, "getRecentTasks() failed due to null mRecentTasks");
-            return new ArrayList<>();
+            Log.e(TAG, "getRecentTasks() failed due to null mRecentTasks");
+            throw new GetRecentTasksException("null mRecentTasks");
         }
         try {
             final GroupedRecentTaskInfo[] rawTasks = mRecentTasks.getRecentTasks(numTasks,
@@ -1407,8 +1423,8 @@
             }
             return new ArrayList<>(Arrays.asList(rawTasks));
         } catch (RemoteException e) {
-            Log.w(TAG, "Failed call getRecentTasks", e);
-            return new ArrayList<>();
+            Log.e(TAG, "Failed call getRecentTasks", e);
+            throw new GetRecentTasksException("Failed call getRecentTasks", e);
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index 80902e3..f4ff4b2 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -184,6 +184,10 @@
             return mTaskContainer.getTaskView();
         }
 
+        public View getSnapshotView() {
+            return mTaskContainer.getSnapshotView();
+        }
+
         /**
          * Called when the current task is interactive for the user
          */
@@ -312,7 +316,7 @@
             // the difference between the bitmap bounds and the projected view bounds.
             Matrix boundsToBitmapSpace = new Matrix();
             Matrix thumbnailMatrix = enableRefactorTaskThumbnail()
-                    ? mHelper.getEnabledState().getThumbnailMatrix()
+                    ? mHelper.getThumbnailMatrix()
                     : mTaskContainer.getThumbnailViewDeprecated().getThumbnailMatrix();
             thumbnailMatrix.invert(boundsToBitmapSpace);
             RectF boundsInBitmapSpace = new RectF();
diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
index 77124bf..dad34ac 100644
--- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
@@ -23,7 +23,6 @@
 import static com.android.launcher3.Flags.enableRefactorTaskThumbnail;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_FREE_FORM_TAP;
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
-import static com.android.window.flags.Flags.enableDesktopWindowingMode;
 
 import android.app.ActivityOptions;
 import android.graphics.Bitmap;
@@ -63,6 +62,7 @@
 import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
 import com.android.systemui.shared.recents.view.RecentsTransition;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 
 import java.util.Collections;
 import java.util.List;
@@ -394,7 +394,7 @@
             return Settings.Global.getInt(
                     container.asContext().getContentResolver(),
                     Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT, 0) != 0
-                    && !enableDesktopWindowingMode();
+                    && !DesktopModeStatus.canEnterDesktopMode(container.asContext());
         }
     };
 
diff --git a/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java b/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
index 5ac04da..717f6c8 100644
--- a/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
+++ b/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
@@ -39,7 +39,8 @@
 import android.util.Log;
 import android.util.Xml;
 
-import com.android.launcher3.AutoInstallsLayout;
+import androidx.annotation.VisibleForTesting;
+
 import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.R;
 import com.android.launcher3.logging.InstanceId;
@@ -73,7 +74,6 @@
             new MainThreadInitializedObject<>(SettingsChangeLogger::new);
 
     private static final String TAG = "SettingsChangeLogger";
-    private static final String ROOT_TAG = "androidx.preference.PreferenceScreen";
     private static final String BOOLEAN_PREF = "SwitchPreference";
 
     private final Context mContext;
@@ -85,8 +85,13 @@
     private StatsLogManager.LauncherEvent mHomeScreenSuggestionEvent;
 
     private SettingsChangeLogger(Context context) {
+        this(context, StatsLogManager.newInstance(context));
+    }
+
+    @VisibleForTesting
+    SettingsChangeLogger(Context context, StatsLogManager statsLogManager) {
         mContext = context;
-        mStatsLogManager = StatsLogManager.newInstance(mContext);
+        mStatsLogManager = statsLogManager;
         mLoggablePrefs = loadPrefKeys(context);
         DisplayController.INSTANCE.get(context).addChangeListener(this);
         mNavMode = DisplayController.getNavigationMode(context);
@@ -105,7 +110,13 @@
         ArrayMap<String, LoggablePref> result = new ArrayMap<>();
 
         try {
-            AutoInstallsLayout.beginDocument(parser, ROOT_TAG);
+            // Move cursor to first tag because it could be
+            // androidx.preference.PreferenceScreen or PreferenceScreen
+            int eventType = parser.getEventType();
+            while (eventType != XmlPullParser.START_TAG
+                    && eventType != XmlPullParser.END_DOCUMENT) {
+                eventType = parser.next();
+            }
             final int depth = parser.getDepth();
             int type;
             while (((type = parser.next()) != XmlPullParser.END_TAG
@@ -189,13 +200,19 @@
                 prefs.getBoolean(key, lp.defaultValue) ? lp.eventIdOn : lp.eventIdOff));
     }
 
+    @VisibleForTesting
+    ArrayMap<String, LoggablePref> getLoggingPrefs() {
+        return mLoggablePrefs;
+    }
+
     @Override
     public void close() {
         getPrefs(mContext).unregisterOnSharedPreferenceChangeListener(this);
         getDevicePrefs(mContext).unregisterOnSharedPreferenceChangeListener(this);
     }
 
-    private static class LoggablePref {
+    @VisibleForTesting
+    static class LoggablePref {
         public boolean defaultValue;
         public int eventIdOn;
         public int eventIdOff;
diff --git a/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt b/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
new file mode 100644
index 0000000..7a5a714
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
@@ -0,0 +1,251 @@
+/*
+ * 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.di
+
+import android.content.Context
+import android.util.Log
+import android.view.View
+import com.android.quickstep.RecentsModel
+import com.android.quickstep.recents.data.RecentTasksRepository
+import com.android.quickstep.recents.data.TasksRepository
+import com.android.quickstep.recents.usecase.GetThumbnailPositionUseCase
+import com.android.quickstep.recents.usecase.GetThumbnailUseCase
+import com.android.quickstep.recents.viewmodel.RecentsViewData
+import com.android.quickstep.task.viewmodel.TaskContainerData
+import com.android.quickstep.task.viewmodel.TaskOverlayViewModel
+import com.android.quickstep.task.viewmodel.TaskThumbnailViewModel
+import com.android.quickstep.task.viewmodel.TaskViewData
+import com.android.quickstep.task.viewmodel.TaskViewModel
+import com.android.quickstep.views.TaskViewType
+import com.android.systemui.shared.recents.model.Task
+import java.util.logging.Level
+
+internal typealias RecentsScopeId = String
+
+class RecentsDependencies private constructor(private val appContext: Context) {
+    private val scopes = mutableMapOf<RecentsScopeId, RecentsDependenciesScope>()
+
+    init {
+        startDefaultScope(appContext)
+    }
+
+    /**
+     * This function initialised the default scope with RecentsView dependencies. These dependencies
+     * are used multiple times and should be a singleton to share across Recents classes.
+     */
+    private fun startDefaultScope(appContext: Context) {
+        createScope(DEFAULT_SCOPE_ID).apply {
+            set(RecentsViewData::class.java.simpleName, RecentsViewData())
+
+            // Create RecentsTaskRepository singleton
+            val recentTasksRepository: RecentTasksRepository =
+                with(RecentsModel.INSTANCE.get(appContext)) {
+                    TasksRepository(this, thumbnailCache, iconCache)
+                }
+            set(RecentTasksRepository::class.java.simpleName, recentTasksRepository)
+        }
+    }
+
+    inline fun <reified T> inject(
+        scopeId: RecentsScopeId = "",
+        extras: RecentsDependenciesExtras = RecentsDependenciesExtras(),
+        noinline factory: ((extras: RecentsDependenciesExtras) -> T)? = null,
+    ): T = inject(T::class.java, scopeId = scopeId, extras = extras, factory = factory)
+
+    @Suppress("UNCHECKED_CAST")
+    @JvmOverloads
+    fun <T> inject(
+        modelClass: Class<T>,
+        scopeId: RecentsScopeId = DEFAULT_SCOPE_ID,
+        extras: RecentsDependenciesExtras = RecentsDependenciesExtras(),
+        factory: ((extras: RecentsDependenciesExtras) -> T)? = null,
+    ): T {
+        val currentScopeId = scopeId.ifEmpty { DEFAULT_SCOPE_ID }
+        val scope = scopes[currentScopeId] ?: createScope(currentScopeId)
+
+        log("inject ${modelClass.simpleName} into ${scope.scopeId}", Log.INFO)
+        var instance: T?
+        synchronized(this) {
+            instance = getDependency(scope, modelClass)
+            log("found instance? $instance", Log.INFO)
+            if (instance == null) {
+                instance =
+                    factory?.invoke(extras) as T ?: createDependency(modelClass, scopeId, extras)
+                scope[modelClass.simpleName] = instance!!
+            }
+        }
+        return instance!!
+    }
+
+    inline fun <reified T> provide(scopeId: RecentsScopeId = "", noinline factory: () -> T): T =
+        provide(T::class.java, scopeId = scopeId, factory = factory)
+
+    @JvmOverloads
+    fun <T> provide(
+        modelClass: Class<T>,
+        scopeId: RecentsScopeId = DEFAULT_SCOPE_ID,
+        factory: () -> T,
+    ) = inject(modelClass, scopeId, factory = { factory.invoke() })
+
+    private fun <T> getDependency(scope: RecentsDependenciesScope, modelClass: Class<T>): T? {
+        var instance: T? = scope[modelClass.simpleName] as T?
+        if (instance == null) {
+            instance =
+                scope.scopeIdsLinked.firstNotNullOfOrNull { scopeId ->
+                    getScope(scopeId)[modelClass.simpleName]
+                } as T?
+        }
+        if (instance != null) log("Found dependency: $instance", Log.INFO)
+        return instance
+    }
+
+    fun getScope(scope: Any): RecentsDependenciesScope {
+        val scopeId: RecentsScopeId = scope as? RecentsScopeId ?: scope.hashCode().toString()
+        return getScope(scopeId)
+    }
+
+    fun getScope(scopeId: RecentsScopeId): RecentsDependenciesScope =
+        scopes[scopeId] ?: createScope(scopeId)
+
+    // TODO(b/353912757): Create a factory so we can prevent this method of growing indefinitely.
+    //  Each class should be responsible for providing a factory function to create a new instance.
+    @Suppress("UNCHECKED_CAST")
+    private fun <T> createDependency(
+        modelClass: Class<T>,
+        scopeId: RecentsScopeId,
+        extras: RecentsDependenciesExtras,
+    ): T {
+        log("createDependency ${modelClass.simpleName} with $scopeId and $extras", Log.WARN)
+        val instance: Any =
+            when (modelClass) {
+                RecentTasksRepository::class.java -> {
+                    with(RecentsModel.INSTANCE.get(appContext)) {
+                        TasksRepository(this, thumbnailCache, iconCache)
+                    }
+                }
+                RecentsViewData::class.java -> RecentsViewData()
+                TaskViewModel::class.java -> TaskViewModel(taskViewData = inject(scopeId, extras))
+                TaskViewData::class.java -> {
+                    val taskViewType = extras["TaskViewType"] as TaskViewType
+                    TaskViewData(taskViewType)
+                }
+                TaskContainerData::class.java -> TaskContainerData()
+                TaskThumbnailViewModel::class.java ->
+                    TaskThumbnailViewModel(
+                        recentsViewData = inject(),
+                        taskViewData = inject(scopeId, extras),
+                        taskContainerData = inject(),
+                        getThumbnailPositionUseCase = inject(),
+                        tasksRepository = inject()
+                    )
+                TaskOverlayViewModel::class.java -> {
+                    val task = extras["Task"] as Task
+                    TaskOverlayViewModel(
+                        task = task,
+                        recentsViewData = inject(),
+                        recentTasksRepository = inject(),
+                        getThumbnailPositionUseCase = inject()
+                    )
+                }
+                GetThumbnailUseCase::class.java -> GetThumbnailUseCase(taskRepository = inject())
+                GetThumbnailPositionUseCase::class.java ->
+                    GetThumbnailPositionUseCase(
+                        deviceProfileRepository = inject(),
+                        rotationStateRepository = inject(),
+                        tasksRepository = inject()
+                    )
+                else -> {
+                    log("Factory for ${modelClass.simpleName} not defined!", Log.ERROR)
+                    error("Factory for ${modelClass.simpleName} not defined!")
+                }
+            }
+        return instance as T
+    }
+
+    private fun createScope(scopeId: RecentsScopeId): RecentsDependenciesScope {
+        return RecentsDependenciesScope(scopeId).also { scopes[scopeId] = it }
+    }
+
+    private fun log(message: String, @Log.Level level: Int = Log.DEBUG) {
+        if (DEBUG) {
+            when (level) {
+                Log.WARN -> Log.w(TAG, message)
+                Log.VERBOSE -> Log.v(TAG, message)
+                Log.INFO -> Log.i(TAG, message)
+                Log.ERROR -> Log.e(TAG, message)
+                else -> Log.d(TAG, message)
+            }
+        }
+    }
+
+    companion object {
+        private const val DEFAULT_SCOPE_ID = "RecentsDependencies::GlobalScope"
+        private const val TAG = "RecentsDependencies"
+        private const val DEBUG = false
+
+        @Volatile private lateinit var instance: RecentsDependencies
+
+        fun initialize(view: View): RecentsDependencies = initialize(view.context)
+
+        fun initialize(context: Context): RecentsDependencies {
+            synchronized(this) {
+                if (!Companion::instance.isInitialized) {
+                    instance = RecentsDependencies(context.applicationContext)
+                }
+            }
+            return instance
+        }
+
+        fun getInstance(): RecentsDependencies {
+            if (!Companion::instance.isInitialized) {
+                throw UninitializedPropertyAccessException(
+                    "Recents dependencies are not initialized. " +
+                        "Call `RecentsDependencies.initialize` before using this container."
+                )
+            }
+            return instance
+        }
+
+        fun destroy() {
+            instance.scopes.clear()
+            instance.startDefaultScope(instance.appContext)
+        }
+    }
+}
+
+inline fun <reified T> RecentsDependencies.Companion.inject(
+    scope: Any = "",
+    vararg extras: Pair<String, Any>,
+    noinline factory: ((extras: RecentsDependenciesExtras) -> T)? = null,
+): Lazy<T> = lazy { get(scope, RecentsDependenciesExtras(extras), factory) }
+
+inline fun <reified T> RecentsDependencies.Companion.get(
+    scope: Any = "",
+    extras: RecentsDependenciesExtras = RecentsDependenciesExtras(),
+    noinline factory: ((extras: RecentsDependenciesExtras) -> T)? = null,
+): T {
+    val scopeId: RecentsScopeId = scope as? RecentsScopeId ?: scope.hashCode().toString()
+    return getInstance().inject(scopeId, extras, factory)
+}
+
+inline fun <reified T> RecentsDependencies.Companion.get(
+    scope: Any = "",
+    vararg extras: Pair<String, Any>,
+    noinline factory: ((extras: RecentsDependenciesExtras) -> T)? = null,
+): T = get(scope, RecentsDependenciesExtras(extras), factory)
+
+fun RecentsDependencies.Companion.getScope(scopeId: Any) = getInstance().getScope(scopeId)
diff --git a/quickstep/src/com/android/quickstep/recents/di/RecentsDependenciesExtras.kt b/quickstep/src/com/android/quickstep/recents/di/RecentsDependenciesExtras.kt
new file mode 100644
index 0000000..753cb6e
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/di/RecentsDependenciesExtras.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.di
+
+data class RecentsDependenciesExtras(private val data: MutableMap<String, Any> = mutableMapOf()) {
+    constructor(value: Array<out Pair<String, Any>>) : this(value.toMap().toMutableMap())
+
+    operator fun get(key: String) = data[key]
+
+    operator fun set(key: String, value: Any) {
+        data[key] = value
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/recents/di/RecentsDependenciesScope.kt b/quickstep/src/com/android/quickstep/recents/di/RecentsDependenciesScope.kt
new file mode 100644
index 0000000..56bb1ed
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/di/RecentsDependenciesScope.kt
@@ -0,0 +1,71 @@
+/*
+ * 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.di
+
+import android.util.Log
+
+class RecentsDependenciesScope(
+    val scopeId: RecentsScopeId,
+    private val dependencies: MutableMap<String, Any> = mutableMapOf(),
+    private val scopeIds: MutableList<RecentsScopeId> = mutableListOf()
+) {
+    val scopeIdsLinked: List<RecentsScopeId>
+        get() = scopeIds.toList()
+
+    operator fun get(identifier: String): Any? {
+        log("get $identifier")
+        return dependencies[identifier]
+    }
+
+    operator fun set(key: String, value: Any) {
+        synchronized(this) {
+            log("set $key")
+            dependencies[key] = value
+        }
+    }
+
+    fun remove(key: String): Any? {
+        synchronized(this) {
+            log("remove $key")
+            return dependencies.remove(key)
+        }
+    }
+
+    fun linkTo(scope: RecentsDependenciesScope) {
+        log("linking to ${scope.scopeId}")
+        scopeIds += scope.scopeId
+    }
+
+    fun close() {
+        log("reset")
+        synchronized(this) { dependencies.clear() }
+    }
+
+    private fun log(message: String) {
+        if (DEBUG) Log.d(TAG, "[scopeId=$scopeId] $message")
+    }
+
+    override fun toString(): String =
+        "scopeId: $scopeId" +
+            "\n dependencies: ${dependencies.map { "${it.key}=${it.value}" }.joinToString(", ")}" +
+            "\n linked to: ${scopeIds.joinToString(", ")}"
+
+    private companion object {
+        private const val TAG = "RecentsDependenciesScope"
+        private const val DEBUG = false
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/recents/usecase/GetThumbnailPositionUseCase.kt b/quickstep/src/com/android/quickstep/recents/usecase/GetThumbnailPositionUseCase.kt
new file mode 100644
index 0000000..f060d7d
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/usecase/GetThumbnailPositionUseCase.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.quickstep.recents.usecase
+
+import android.graphics.Matrix
+import android.graphics.Rect
+import com.android.quickstep.recents.data.RecentTasksRepository
+import com.android.quickstep.recents.data.RecentsDeviceProfileRepository
+import com.android.quickstep.recents.data.RecentsRotationStateRepository
+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()
+) {
+    suspend fun run(taskId: Int, width: Int, height: Int, isRtl: Boolean): ThumbnailPositionState {
+        val thumbnailData =
+            tasksRepository.getThumbnailById(taskId).firstOrNull() ?: return MissingThumbnail
+        val thumbnail = thumbnailData.thumbnail ?: return MissingThumbnail
+        previewPositionHelper.updateThumbnailMatrix(
+            Rect(0, 0, thumbnail.width, thumbnail.height),
+            thumbnailData,
+            width,
+            height,
+            deviceProfileRepository.getRecentsDeviceProfile().isLargeScreen,
+            rotationStateRepository.getRecentsRotationState().activityRotation,
+            isRtl
+        )
+        return MatrixScaling(
+            previewPositionHelper.matrix,
+            previewPositionHelper.isOrientationChanged
+        )
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/task/util/GetThumbnailUseCase.kt b/quickstep/src/com/android/quickstep/recents/usecase/GetThumbnailUseCase.kt
similarity index 95%
rename from quickstep/src/com/android/quickstep/task/util/GetThumbnailUseCase.kt
rename to quickstep/src/com/android/quickstep/recents/usecase/GetThumbnailUseCase.kt
index e8dd04c3..3aa808e 100644
--- a/quickstep/src/com/android/quickstep/task/util/GetThumbnailUseCase.kt
+++ b/quickstep/src/com/android/quickstep/recents/usecase/GetThumbnailUseCase.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.quickstep.task.util
+package com.android.quickstep.recents.usecase
 
 import android.graphics.Bitmap
 import com.android.quickstep.recents.data.RecentTasksRepository
diff --git a/quickstep/src/com/android/quickstep/recents/usecase/ThumbnailPositionState.kt b/quickstep/src/com/android/quickstep/recents/usecase/ThumbnailPositionState.kt
new file mode 100644
index 0000000..1a1bef7
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/usecase/ThumbnailPositionState.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.usecase
+
+import android.graphics.Matrix
+
+/** State on how a task Thumbnail can fit on given canvas */
+sealed class ThumbnailPositionState {
+    data object MissingThumbnail : ThumbnailPositionState()
+
+    data class MatrixScaling(val matrix: Matrix, val isRotated: Boolean) : ThumbnailPositionState()
+}
diff --git a/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt b/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt
new file mode 100644
index 0000000..8b03a84
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.recents.viewmodel
+
+import com.android.quickstep.recents.data.RecentTasksRepository
+
+class RecentsViewModel(
+    private val recentsTasksRepository: RecentTasksRepository,
+    private val recentsViewData: RecentsViewData
+) {
+    fun refreshAllTaskData() {
+        recentsTasksRepository.getAllTaskData(true)
+    }
+
+    fun updateVisibleTasks(visibleTaskIdList: List<Int>) {
+        recentsTasksRepository.setVisibleTasks(visibleTaskIdList)
+    }
+
+    fun updateScale(scale: Float) {
+        recentsViewData.scale.value = scale
+    }
+
+    fun updateFullscreenProgress(fullscreenProgress: Float) {
+        recentsViewData.fullscreenProgress.value = fullscreenProgress
+    }
+
+    fun updateTasksFullyVisible(taskIds: Set<Int>) {
+        recentsViewData.settledFullyVisibleTaskIds.value = taskIds
+    }
+
+    fun setOverlayEnabled(isOverlayEnabled: Boolean) {
+        recentsViewData.overlayEnabled.value = isOverlayEnabled
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskOverlayUiState.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskOverlayUiState.kt
index feee11f..5fb5b90 100644
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskOverlayUiState.kt
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskOverlayUiState.kt
@@ -17,15 +17,10 @@
 package com.android.quickstep.task.thumbnail
 
 import android.graphics.Bitmap
-import android.graphics.Matrix
 
 /** Ui state for [com.android.quickstep.TaskOverlayFactory.TaskOverlay] */
 sealed class TaskOverlayUiState {
     data object Disabled : TaskOverlayUiState()
 
-    data class Enabled(
-        val isRealSnapshot: Boolean,
-        val thumbnail: Bitmap?,
-        val thumbnailMatrix: Matrix
-    ) : TaskOverlayUiState()
+    data class Enabled(val isRealSnapshot: Boolean, val thumbnail: Bitmap?) : TaskOverlayUiState()
 }
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailUiState.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailUiState.kt
index 40f9b28..3b3a811 100644
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailUiState.kt
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailUiState.kt
@@ -17,18 +17,17 @@
 package com.android.quickstep.task.thumbnail
 
 import android.graphics.Bitmap
-import android.graphics.Rect
 import androidx.annotation.ColorInt
 
 sealed class TaskThumbnailUiState {
     data object Uninitialized : TaskThumbnailUiState()
+
     data object LiveTile : TaskThumbnailUiState()
+
     data class BackgroundOnly(@ColorInt val backgroundColor: Int) : TaskThumbnailUiState()
-    data class Snapshot(
-        val bitmap: Bitmap,
-        val drawnRect: Rect,
-        @ColorInt val backgroundColor: Int
-    ) : TaskThumbnailUiState()
+
+    data class Snapshot(val bitmap: Bitmap, @ColorInt val backgroundColor: Int) :
+        TaskThumbnailUiState()
 }
 
 data class TaskThumbnail(val taskId: Int, val isRunning: Boolean)
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
index d22fc94..fcc2af3 100644
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
@@ -31,14 +31,14 @@
 import com.android.launcher3.R
 import com.android.launcher3.Utilities
 import com.android.launcher3.util.ViewPool
+import com.android.quickstep.recents.di.RecentsDependencies
+import com.android.quickstep.recents.di.inject
 import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.BackgroundOnly
 import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.LiveTile
 import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Snapshot
 import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized
+import com.android.quickstep.task.viewmodel.TaskThumbnailViewModel
 import com.android.quickstep.util.TaskCornerRadius
-import com.android.quickstep.views.RecentsView
-import com.android.quickstep.views.RecentsViewContainer
-import com.android.quickstep.views.TaskView
 import com.android.systemui.shared.system.QuickStepContract
 import kotlinx.coroutines.CoroutineName
 import kotlinx.coroutines.CoroutineScope
@@ -49,26 +49,16 @@
 import kotlinx.coroutines.flow.onEach
 
 class TaskThumbnailView : FrameLayout, ViewPool.Reusable {
-    // TODO(b/335649589): Ideally create and obtain this from DI. This ViewModel should be scoped
-    //  to [TaskView], and also shared between [TaskView] and [TaskThumbnailView]
-    //  This is using a lazy for now because the dependencies cannot be obtained without DI.
-    val viewModel by lazy {
-        val recentsView =
-            RecentsViewContainer.containerFromContext<RecentsViewContainer>(context)
-                .getOverviewPanel<RecentsView<*, *>>()
-        TaskThumbnailViewModel(
-            recentsView.mRecentsViewData!!,
-            (parent as TaskView).taskViewData,
-            (parent as TaskView).getTaskContainerForTaskThumbnailView(this)!!.taskContainerData,
-            recentsView.mTasksRepository!!,
-        )
-    }
+
+    private val viewModel: TaskThumbnailViewModel by RecentsDependencies.inject(this)
+
     private lateinit var viewAttachedScope: CoroutineScope
 
     private val scrimView: View by lazy { findViewById(R.id.task_thumbnail_scrim) }
     private val liveTileView: LiveTileView by lazy { findViewById(R.id.task_thumbnail_live_tile) }
-    private val thumbnail: ImageView by lazy { findViewById(R.id.task_thumbnail) }
+    private val thumbnailView: ImageView by lazy { findViewById(R.id.task_thumbnail) }
 
+    private var uiState: TaskThumbnailUiState = Uninitialized
     private var inheritedScale: Float = 1f
 
     private val _measuredBounds = Rect()
@@ -97,6 +87,7 @@
             CoroutineScope(SupervisorJob() + Dispatchers.Main + CoroutineName("TaskThumbnailView"))
         viewModel.uiState
             .onEach { viewModelUiState ->
+                uiState = viewModelUiState
                 resetViews()
                 when (viewModelUiState) {
                     is Uninitialized -> {}
@@ -135,7 +126,14 @@
     }
 
     override fun onRecycle() {
-        // Do nothing
+        uiState = Uninitialized
+    }
+
+    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
+        super.onSizeChanged(w, h, oldw, oldh)
+        if (uiState is Snapshot) {
+            setImageMatrix()
+        }
     }
 
     override fun onConfigurationChanged(newConfig: Configuration?) {
@@ -148,7 +146,7 @@
 
     private fun resetViews() {
         liveTileView.isVisible = false
-        thumbnail.isVisible = false
+        thumbnailView.isVisible = false
         scrimView.alpha = 0f
         setBackgroundColor(Color.BLACK)
     }
@@ -163,8 +161,13 @@
 
     private fun drawSnapshot(snapshot: Snapshot) {
         drawBackground(snapshot.backgroundColor)
-        thumbnail.setImageBitmap(snapshot.bitmap)
-        thumbnail.isVisible = true
+        thumbnailView.setImageBitmap(snapshot.bitmap)
+        thumbnailView.isVisible = true
+        setImageMatrix()
+    }
+
+    private fun setImageMatrix() {
+        thumbnailView.imageMatrix = viewModel.getThumbnailPositionState(width, height, isLayoutRtl)
     }
 
     private fun getCurrentCornerRadius() =
diff --git a/quickstep/src/com/android/quickstep/task/util/TaskOverlayHelper.kt b/quickstep/src/com/android/quickstep/task/util/TaskOverlayHelper.kt
index a9f7041..9253dbf 100644
--- a/quickstep/src/com/android/quickstep/task/util/TaskOverlayHelper.kt
+++ b/quickstep/src/com/android/quickstep/task/util/TaskOverlayHelper.kt
@@ -18,12 +18,12 @@
 
 import android.util.Log
 import com.android.quickstep.TaskOverlayFactory
+import com.android.quickstep.recents.di.RecentsDependencies
+import com.android.quickstep.recents.di.get
 import com.android.quickstep.task.thumbnail.TaskOverlayUiState
 import com.android.quickstep.task.thumbnail.TaskOverlayUiState.Disabled
 import com.android.quickstep.task.thumbnail.TaskOverlayUiState.Enabled
 import com.android.quickstep.task.viewmodel.TaskOverlayViewModel
-import com.android.quickstep.views.RecentsView
-import com.android.quickstep.views.RecentsViewContainer
 import com.android.systemui.shared.recents.model.Task
 import kotlinx.coroutines.CoroutineName
 import kotlinx.coroutines.CoroutineScope
@@ -41,48 +41,62 @@
     private lateinit var overlayInitializedScope: CoroutineScope
     private var uiState: TaskOverlayUiState = Disabled
 
-    // TODO(b/335649589): Ideally create and obtain this from DI. This ViewModel should be scoped
-    //  to [TaskView], and also shared between [TaskView] and [TaskThumbnailView]
-    //  This is using a lazy for now because the dependencies cannot be obtained without DI.
-    private val taskOverlayViewModel by lazy {
-        val recentsView =
-            RecentsViewContainer.containerFromContext<RecentsViewContainer>(
-                    overlay.taskView.context
-                )
-                .getOverviewPanel<RecentsView<*, *>>()
-        TaskOverlayViewModel(task, recentsView.mRecentsViewData!!, recentsView.mTasksRepository!!)
+    private val viewModel: TaskOverlayViewModel by lazy {
+        TaskOverlayViewModel(
+            task = task,
+            recentsViewData = RecentsDependencies.get(),
+            getThumbnailPositionUseCase = RecentsDependencies.get(),
+            recentTasksRepository = RecentsDependencies.get()
+        )
     }
 
     // TODO(b/331753115): TaskOverlay should listen for state changes and react.
     val enabledState: Enabled
         get() = uiState as Enabled
 
+    fun getThumbnailMatrix() = getThumbnailPositionState().matrix
+
+    private fun getThumbnailPositionState() =
+        viewModel.getThumbnailPositionState(
+            overlay.snapshotView.width,
+            overlay.snapshotView.height,
+            overlay.snapshotView.isLayoutRtl
+        )
+
     fun init() {
         overlayInitializedScope =
             CoroutineScope(SupervisorJob() + Dispatchers.Main + CoroutineName("TaskOverlayHelper"))
-        taskOverlayViewModel.overlayState
+        viewModel.overlayState
             .onEach {
                 uiState = it
                 if (it is Enabled) {
-                    Log.d(TAG, "initOverlay - taskId: ${task.key.id}, thumbnail: ${it.thumbnail}")
-                    overlay.initOverlay(
-                        task,
-                        it.thumbnail,
-                        it.thumbnailMatrix,
-                        /* rotated= */ false
-                    )
+                    initOverlay(it)
                 } else {
-                    Log.d(TAG, "reset - taskId: ${task.key.id}")
-                    overlay.reset()
+                    reset()
                 }
             }
             .launchIn(overlayInitializedScope)
+        overlay.snapshotView.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
+            (uiState as? Enabled)?.let { initOverlay(it) }
+        }
+    }
+
+    private fun initOverlay(enabledState: Enabled) {
+        Log.d(TAG, "initOverlay - taskId: ${task.key.id}, thumbnail: ${enabledState.thumbnail}")
+        with(getThumbnailPositionState()) {
+            overlay.initOverlay(task, enabledState.thumbnail, matrix, isRotated)
+        }
+    }
+
+    private fun reset() {
+        Log.d(TAG, "reset - taskId: ${task.key.id}")
+        overlay.reset()
     }
 
     fun destroy() {
         overlayInitializedScope.cancel()
         uiState = Disabled
-        overlay.reset()
+        reset()
     }
 
     companion object {
diff --git a/quickstep/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModel.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModel.kt
index 47f32fb..4e13d1c 100644
--- a/quickstep/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModel.kt
+++ b/quickstep/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModel.kt
@@ -18,6 +18,9 @@
 
 import android.graphics.Matrix
 import com.android.quickstep.recents.data.RecentTasksRepository
+import com.android.quickstep.recents.usecase.GetThumbnailPositionUseCase
+import com.android.quickstep.recents.usecase.ThumbnailPositionState.MatrixScaling
+import com.android.quickstep.recents.usecase.ThumbnailPositionState.MissingThumbnail
 import com.android.quickstep.recents.viewmodel.RecentsViewData
 import com.android.quickstep.task.thumbnail.TaskOverlayUiState.Disabled
 import com.android.quickstep.task.thumbnail.TaskOverlayUiState.Enabled
@@ -25,31 +28,52 @@
 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(
-    task: Task,
+    private val task: Task,
     recentsViewData: RecentsViewData,
-    tasksRepository: RecentTasksRepository,
+    private val getThumbnailPositionUseCase: GetThumbnailPositionUseCase,
+    recentTasksRepository: RecentTasksRepository,
 ) {
     val overlayState =
         combine(
                 recentsViewData.overlayEnabled,
                 recentsViewData.settledFullyVisibleTaskIds.map { it.contains(task.key.id) },
-                tasksRepository.getThumbnailById(task.key.id)
+                recentTasksRepository.getThumbnailById(task.key.id)
             ) { isOverlayEnabled, isFullyVisible, thumbnailData ->
                 if (isOverlayEnabled && isFullyVisible) {
                     Enabled(
                         isRealSnapshot = (thumbnailData?.isRealSnapshot ?: false) && !task.isLocked,
                         thumbnailData?.thumbnail,
-                        // TODO(b/343101424): Use PreviewPositionHelper, listen from a common source
-                        // with
-                        //  TaskThumbnailView.
-                        Matrix.IDENTITY_MATRIX
                     )
                 } else {
                     Disabled
                 }
             }
             .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
+                }
+            }
+            ThumbnailPositionState(matrix, isRotated)
+        }
+    }
+
+    data class ThumbnailPositionState(val matrix: Matrix, val isRotated: Boolean)
 }
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModel.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModel.kt
similarity index 75%
rename from quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModel.kt
rename to quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModel.kt
index d8729a6..6465645 100644
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModel.kt
+++ b/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModel.kt
@@ -14,19 +14,21 @@
  * limitations under the License.
  */
 
-package com.android.quickstep.task.thumbnail
+package com.android.quickstep.task.viewmodel
 
 import android.annotation.ColorInt
-import android.graphics.Rect
+import android.graphics.Matrix
 import androidx.core.graphics.ColorUtils
 import com.android.quickstep.recents.data.RecentTasksRepository
+import com.android.quickstep.recents.usecase.GetThumbnailPositionUseCase
+import com.android.quickstep.recents.usecase.ThumbnailPositionState
 import com.android.quickstep.recents.viewmodel.RecentsViewData
+import com.android.quickstep.task.thumbnail.TaskThumbnail
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState
 import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.BackgroundOnly
 import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.LiveTile
 import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Snapshot
 import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized
-import com.android.quickstep.task.viewmodel.TaskContainerData
-import com.android.quickstep.task.viewmodel.TaskViewData
 import com.android.systemui.shared.recents.model.Task
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
@@ -37,6 +39,7 @@
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.runBlocking
 
 @OptIn(ExperimentalCoroutinesApi::class)
 class TaskThumbnailViewModel(
@@ -44,9 +47,10 @@
     taskViewData: TaskViewData,
     taskContainerData: TaskContainerData,
     private val tasksRepository: RecentTasksRepository,
+    private val getThumbnailPositionUseCase: GetThumbnailPositionUseCase
 ) {
     private val task = MutableStateFlow<Flow<Task?>>(flowOf(null))
-    private var boundTaskIsRunning = false
+    private lateinit var taskThumbnail: TaskThumbnail
 
     /**
      * Progress for changes in corner radius. progress: 0 = overview corner radius; 1 = fullscreen
@@ -66,16 +70,12 @@
                 taskFlow.map { taskVal ->
                     when {
                         taskVal == null -> Uninitialized
-                        boundTaskIsRunning -> LiveTile
+                        taskThumbnail.isRunning -> LiveTile
                         isBackgroundOnly(taskVal) ->
                             BackgroundOnly(taskVal.colorBackground.removeAlpha())
                         isSnapshotState(taskVal) -> {
                             val bitmap = taskVal.thumbnail?.thumbnail!!
-                            Snapshot(
-                                bitmap,
-                                Rect(0, 0, bitmap.width, bitmap.height),
-                                taskVal.colorBackground.removeAlpha()
-                            )
+                            Snapshot(bitmap, taskVal.colorBackground.removeAlpha())
                         }
                         else -> Uninitialized
                     }
@@ -84,10 +84,22 @@
             .distinctUntilChanged()
 
     fun bind(taskThumbnail: TaskThumbnail) {
-        boundTaskIsRunning = taskThumbnail.isRunning
+        this.taskThumbnail = taskThumbnail
         task.value = tasksRepository.getTaskDataById(taskThumbnail.taskId)
     }
 
+    fun getThumbnailPositionState(width: Int, height: Int, isRtl: Boolean): Matrix {
+        return runBlocking {
+            when (
+                val thumbnailPositionState =
+                    getThumbnailPositionUseCase.run(taskThumbnail.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
 
     private fun isSnapshotState(task: Task): Boolean {
diff --git a/quickstep/src/com/android/quickstep/task/viewmodel/TaskViewModel.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskViewModel.kt
new file mode 100644
index 0000000..ec75d59
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/task/viewmodel/TaskViewModel.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.task.viewmodel
+
+import androidx.lifecycle.ViewModel
+
+class TaskViewModel(private val taskViewData: TaskViewData) : ViewModel() {
+    fun updateScale(scale: Float) {
+        taskViewData.scale.value = scale
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index d888eb9..a3d6359 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -191,10 +191,12 @@
 import com.android.quickstep.TopTaskTracker;
 import com.android.quickstep.ViewUtils;
 import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
+import com.android.quickstep.recents.data.RecentTasksRepository;
 import com.android.quickstep.recents.data.RecentsDeviceProfileRepository;
 import com.android.quickstep.recents.data.RecentsRotationStateRepository;
-import com.android.quickstep.recents.data.TasksRepository;
+import com.android.quickstep.recents.di.RecentsDependencies;
 import com.android.quickstep.recents.viewmodel.RecentsViewData;
+import com.android.quickstep.recents.viewmodel.RecentsViewModel;
 import com.android.quickstep.util.ActiveGestureErrorDetector;
 import com.android.quickstep.util.ActiveGestureLog;
 import com.android.quickstep.util.AnimUtils;
@@ -241,8 +243,9 @@
 
 /**
  * A list of recent tasks.
+ *
  * @param <CONTAINER_TYPE> : the container that should host recents view
- * @param <STATE_TYPE> : the type of base state that will be used
+ * @param <STATE_TYPE>     : the type of base state that will be used
  */
 
 public abstract class RecentsView<CONTAINER_TYPE extends Context & RecentsViewContainer,
@@ -391,7 +394,7 @@
                     view.setScaleX(scale);
                     view.setScaleY(scale);
                     if (enableRefactorTaskThumbnail()) {
-                        view.mRecentsViewData.getScale().setValue(scale);
+                        view.mRecentsViewModel.updateScale(scale);
                     }
                     view.mLastComputedTaskStartPushOutDistance = null;
                     view.mLastComputedTaskEndPushOutDistance = null;
@@ -463,15 +466,6 @@
 
     private static final float FOREGROUND_SCRIM_TINT = 0.32f;
 
-    @Nullable
-    public final RecentsViewData mRecentsViewData = new RecentsViewData();
-    @Nullable
-    public final TasksRepository mTasksRepository;
-    @Nullable
-    public final RecentsRotationStateRepository mOrientedStateRepository;
-    @Nullable
-    public final RecentsDeviceProfileRepository mDeviceProfileRepository;
-
     protected final RecentsOrientedState mOrientationState;
     protected final BaseContainerInterface<STATE_TYPE, CONTAINER_TYPE> mSizeStrategy;
     @Nullable
@@ -726,10 +720,12 @@
 
     private final SplitSelectionListener mSplitSelectionListener = new SplitSelectionListener() {
         @Override
-        public void onSplitSelectionConfirmed() { }
+        public void onSplitSelectionConfirmed() {
+        }
 
         @Override
-        public void onSplitSelectionActive() { }
+        public void onSplitSelectionActive() {
+        }
 
         @Override
         public void onSplitSelectionExit(boolean launchedSplit) {
@@ -808,10 +804,13 @@
      */
     private boolean mAnyTaskHasBeenDismissed;
 
+    private final RecentsViewModel mRecentsViewModel;
+
     public RecentsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
             BaseContainerInterface sizeStrategy) {
         super(context, attrs, defStyleAttr);
         setEnableFreeScroll(true);
+
         mSizeStrategy = sizeStrategy;
         mContainer = RecentsViewContainer.containerFromContext(context);
         mOrientationState = new RecentsOrientedState(
@@ -819,22 +818,30 @@
         final int rotation = mContainer.getDisplay().getRotation();
         mOrientationState.setRecentsRotation(rotation);
 
+        // Start Recents Dependency graph
+        if (enableRefactorTaskThumbnail()) {
+            RecentsDependencies recentsDependencies = RecentsDependencies.Companion.initialize(
+                    this);
+            mRecentsViewModel = new RecentsViewModel(
+                    recentsDependencies.inject(RecentTasksRepository.class),
+                    recentsDependencies.inject(RecentsViewData.class)
+            );
+
+            recentsDependencies.provide(RecentsRotationStateRepository.class,
+                    () -> new RecentsRotationStateRepository(mOrientationState));
+
+            recentsDependencies.provide(RecentsDeviceProfileRepository.class,
+                    () -> new RecentsDeviceProfileRepository(mContainer));
+        } else {
+            mRecentsViewModel = null;
+        }
+
         mScrollHapticMinGapMillis = getResources()
                 .getInteger(R.integer.recentsScrollHapticMinGapMillis);
         mFastFlingVelocity = getResources()
                 .getDimensionPixelSize(R.dimen.recents_fast_fling_velocity);
         mModel = RecentsModel.INSTANCE.get(context);
         mIdp = InvariantDeviceProfile.INSTANCE.get(context);
-        if (enableRefactorTaskThumbnail()) {
-            mTasksRepository = new TasksRepository(
-                    mModel, mModel.getThumbnailCache(), mModel.getIconCache());
-            mOrientedStateRepository = new RecentsRotationStateRepository(mOrientationState);
-            mDeviceProfileRepository = new RecentsDeviceProfileRepository(mContainer);
-        } else {
-            mTasksRepository = null;
-            mOrientedStateRepository = null;
-            mDeviceProfileRepository = null;
-        }
 
         mClearAllButton = (ClearAllButton) LayoutInflater.from(context)
                 .inflate(R.layout.overview_clear_all_button, this, false);
@@ -1077,6 +1084,7 @@
 
     /**
      * Update the thumbnail(s) of the relevant TaskView.
+     *
      * @param refreshNow Refresh immediately if it's true.
      */
     @Nullable
@@ -1137,6 +1145,7 @@
 
     /**
      * See overridden implementations
+     *
      * @return {@code true} if child TaskViews can be launched when user taps on them
      */
     protected boolean canLaunchFullscreenTask() {
@@ -2061,7 +2070,7 @@
     public void setFullscreenProgress(float fullscreenProgress) {
         mFullscreenProgress = fullscreenProgress;
         if (enableRefactorTaskThumbnail()) {
-            mRecentsViewData.getFullscreenProgress().setValue(mFullscreenProgress);
+            mRecentsViewModel.updateFullscreenProgress(mFullscreenProgress);
         }
         int taskCount = getTaskViewCount();
         for (int i = 0; i < taskCount; i++) {
@@ -2459,7 +2468,7 @@
             }
         }
         if (enableRefactorTaskThumbnail()) {
-            mTasksRepository.setVisibleTasks(visibleTaskIds);
+            mRecentsViewModel.updateVisibleTasks(visibleTaskIds);
         }
     }
 
@@ -2526,6 +2535,13 @@
         mFocusedTaskViewId = -1;
         mAnyTaskHasBeenDismissed = false;
 
+
+        if (enableRefactorTaskThumbnail()) {
+            // TODO(b/353917593): RecentsView is never destroyed, so its dependencies need to
+            //  be cleaned up during the reset, but re-created when RecentsView is "resumed".
+            // RecentsDependencies.Companion.destroy();
+        }
+
         Log.d(TAG, "reset - mEnableDrawingLiveTile: " + mEnableDrawingLiveTile
                 + ", mRecentsAnimationController: " + mRecentsAnimationController);
         if (mEnableDrawingLiveTile) {
@@ -2636,6 +2652,7 @@
 
     /**
      * Get the index of the task view whose id matches {@param taskId}.
+     *
      * @return -1 if there is no task view for the task id, else the index of the task view.
      */
     public int getTaskIndexForId(int taskId) {
@@ -2651,7 +2668,7 @@
             mTaskListChangeId = mModel.getTasks(this::applyLoadPlan, RecentsFilterState
                     .getFilter(mFilterState.getPackageNameToFilter()));
             if (enableRefactorTaskThumbnail()) {
-                mTasksRepository.getAllTaskData(/* forceRefresh = */ true);
+                mRecentsViewModel.refreshAllTaskData();
             }
         }
     }
@@ -2820,7 +2837,7 @@
         int[] runningTaskIds = Arrays.stream(runningTasks).mapToInt(task -> task.key.id).toArray();
         TaskView matchingTaskView = null;
         if (hasDesktopTask(runningTasks) && runningTaskIds.length == 1) {
-            // TODO(b/249371338): Unsure if it's expected, desktop runningTasks only have a single
+            // TODO(b/342635213): Unsure if it's expected, desktop runningTasks only have a single
             // taskId, therefore we match any DesktopTaskView that contains the runningTaskId.
             TaskView taskview = getTaskViewByTaskId(runningTaskIds[0]);
             if (taskview instanceof DesktopTaskView) {
@@ -2861,7 +2878,7 @@
                 // When we create a placeholder task view mSplitBoundsConfig will be null, but with
                 // the actual app running we won't need to show the thumbnail until all the tasks
                 // load later anyways
-                ((GroupedTaskView)taskView).bind(mTmpRunningTasks[0], mTmpRunningTasks[1],
+                ((GroupedTaskView) taskView).bind(mTmpRunningTasks[0], mTmpRunningTasks[1],
                         mOrientationState, mTaskOverlayFactory, mSplitBoundsConfig);
             } else {
                 taskView = getTaskViewFromPool(TaskViewType.SINGLE);
@@ -3189,7 +3206,7 @@
                     (mIsRtl
                             ? mLastComputedTaskSize.right
                             : deviceProfile.widthPx - mLastComputedTaskSize.left)
-                    - longRowWidth - deviceProfile.overviewGridSideMargin;
+                            - longRowWidth - deviceProfile.overviewGridSideMargin;
             clearAllShortTotalWidthTranslation = mIsRtl
                     ? -mClearAllShortTotalWidthTranslation : mClearAllShortTotalWidthTranslation;
             if (snappedTaskRowWidth == longRowWidth) {
@@ -3219,9 +3236,9 @@
                     (mIsRtl
                             ? mLastComputedTaskSize.left
                             : deviceProfile.widthPx - mLastComputedTaskSize.right)
-                    - deviceProfile.overviewGridSideMargin - mPageSpacing
-                    + (mTaskWidth - snappedTaskView.getLayoutParams().width)
-                    - mClearAllShortTotalWidthTranslation;
+                            - deviceProfile.overviewGridSideMargin - mPageSpacing
+                            + (mTaskWidth - snappedTaskView.getLayoutParams().width)
+                            - mClearAllShortTotalWidthTranslation;
             if (distanceFromClearAll < minimumDistance) {
                 int distanceDifference = minimumDistance - distanceFromClearAll;
                 snappedTaskGridTranslationX += mIsRtl ? distanceDifference : -distanceDifference;
@@ -3301,12 +3318,12 @@
             mLayoutTransition.addTransitionListener(new TransitionListener() {
                 @Override
                 public void startTransition(LayoutTransition transition, ViewGroup viewGroup,
-                    View view, int i) {
+                        View view, int i) {
                 }
 
                 @Override
                 public void endTransition(LayoutTransition transition, ViewGroup viewGroup,
-                    View view, int i) {
+                        View view, int i) {
                     // When the unpinned task is added, snap to first page and disable transitions
                     if (view instanceof TaskView) {
                         snapToPage(0);
@@ -3458,11 +3475,13 @@
 
     /**
      * Creates a {@link PendingAnimation} for dismissing the specified {@link TaskView}.
-     * @param dismissedTaskView the {@link TaskView} to be dismissed
-     * @param animateTaskView whether the {@link TaskView} to be dismissed should be animated
-     * @param shouldRemoveTask whether the associated {@link Task} should be removed from
-     *                         ActivityManager after dismissal
-     * @param duration duration of the animation
+     *
+     * @param dismissedTaskView           the {@link TaskView} to be dismissed
+     * @param animateTaskView             whether the {@link TaskView} to be dismissed should be
+     *                                    animated
+     * @param shouldRemoveTask            whether the associated {@link Task} should be removed from
+     *                                    ActivityManager after dismissal
+     * @param duration                    duration of the animation
      * @param dismissingForSplitSelection task dismiss animation is used for entering split
      *                                    selection state from app icon
      */
@@ -3727,7 +3746,7 @@
 
                     float animationEndProgress = isSplitSelectionActive()
                             ? Utilities.boundToRange(splitTimings.getGridSlideStartOffset()
-                                            + splitTimings.getGridSlideDurationOffset(), 0f, 1f)
+                            + splitTimings.getGridSlideDurationOffset(), 0f, 1f)
                             : 1f;
 
                     // Slide tiles in horizontally to fill dismissed area
@@ -3771,16 +3790,16 @@
                 // dismissed index or next focused index. Offset successive task dismissal
                 // durations for a staggered effect.
                 distanceFromDismissedTask++;
-                int staggerColumn =  isStagingFocusedTask
+                int staggerColumn = isStagingFocusedTask
                         ? (int) Math.ceil(distanceFromDismissedTask / 2f)
                         : distanceFromDismissedTask;
                 // Set timings based on if user is initiating splitscreen on the focused task,
                 // or splitting/dismissing some other task.
                 float animationStartProgress = isStagingFocusedTask
                         ? Utilities.boundToRange(
-                                splitTimings.getGridSlideStartOffset()
-                                        + (splitTimings.getGridSlideStaggerOffset()
-                                        * staggerColumn),
+                        splitTimings.getGridSlideStartOffset()
+                                + (splitTimings.getGridSlideStaggerOffset()
+                                * staggerColumn),
                         0f,
                         dismissTranslationInterpolationEnd)
                         : Utilities.boundToRange(
@@ -3789,9 +3808,9 @@
                                         * staggerColumn, 0f, dismissTranslationInterpolationEnd);
                 float animationEndProgress = isStagingFocusedTask
                         ? Utilities.boundToRange(
-                                splitTimings.getGridSlideStartOffset()
-                                        + (splitTimings.getGridSlideStaggerOffset() * staggerColumn)
-                                        + splitTimings.getGridSlideDurationOffset(),
+                        splitTimings.getGridSlideStartOffset()
+                                + (splitTimings.getGridSlideStaggerOffset() * staggerColumn)
+                                + splitTimings.getGridSlideDurationOffset(),
                         0f,
                         dismissTranslationInterpolationEnd)
                         : dismissTranslationInterpolationEnd;
@@ -4007,8 +4026,9 @@
                                             RecentsView.this);
                                     int taskSize = (int) (
                                             getPagedOrientationHandler().getMeasuredSize(
-                                            taskView) * taskView
-                                            .getSizeAdjustment(/*fullscreenEnabled=*/false));
+                                                    taskView) * taskView
+                                                    .getSizeAdjustment(/*fullscreenEnabled=*/
+                                                            false));
                                     int taskEnd = taskStart + taskSize;
 
                                     shouldRebalance = taskEnd >= screenEnd - mPageSpacing;
@@ -4651,6 +4671,7 @@
     /**
      * Computes the distance to offset the given child such that it is completely offscreen when
      * translating away from the given midpoint.
+     *
      * @param offsetProgress From 0 to 1 where 0 means no offset and 1 means offset offscreen.
      */
     private float getHorizontalOffsetSize(int childIndex, int midpointIndex, float offsetProgress) {
@@ -4872,7 +4893,7 @@
                             mContainer.getDeviceProfile(),
                             mSplitHiddenTaskView.getWidth(), mSplitHiddenTaskView.getHeight(),
                             primaryTaskSelected);
-            builder.addOnFrameCallback(() ->{
+            builder.addOnFrameCallback(() -> {
                 // TODO(b/334826842): Handle splash icon for new TTV.
                 if (!enableRefactorTaskThumbnail()) {
                     taskContainer.getThumbnailViewDeprecated().refreshSplashView();
@@ -4896,17 +4917,21 @@
      * @param containerTaskView If our second selected app is currently running in Recents, this is
      *                          the "container" TaskView from Recents. If we are starting a fresh
      *                          instance of the app from an Intent, this will be null.
-     * @param task The Task corresponding to our second selected app. If we are starting a fresh
-     *             instance of the app from an Intent, this will be null.
-     * @param drawable The Drawable corresponding to our second selected app's icon.
-     * @param secondView The View representing the current space on the screen where the second app
-     *                   is (either the ThumbnailView or the tapped icon).
-     * @param intent If we are launching a fresh instance of the app, this is the Intent for it. If
-     *               the second app is already running in Recents, this will be null.
-     * @param user If we are launching a fresh instance of the app, this is the UserHandle for it.
-     *             If the second app is already running in Recents, this will be null.
+     * @param task              The Task corresponding to our second selected app. If we are
+     *                          starting a fresh
+     *                          instance of the app from an Intent, this will be null.
+     * @param drawable          The Drawable corresponding to our second selected app's icon.
+     * @param secondView        The View representing the current space on the screen where the
+     *                          second app
+     *                          is (either the ThumbnailView or the tapped icon).
+     * @param intent            If we are launching a fresh instance of the app, this is the Intent
+     *                          for it. If
+     *                          the second app is already running in Recents, this will be null.
+     * @param user              If we are launching a fresh instance of the app, this is the
+     *                          UserHandle for it.
+     *                          If the second app is already running in Recents, this will be null.
      * @return true if waiting for confirmation of second app or if split animations are running,
-     *          false otherwise
+     * false otherwise
      */
     public boolean confirmSplitSelect(TaskView containerTaskView, Task task, Drawable drawable,
             View secondView, @Nullable Bitmap thumbnail, Intent intent, UserHandle user,
@@ -4994,7 +5019,7 @@
                 "Second tile selected");
 
         // Fade out all other views underneath placeholders
-        ObjectAnimator tvFade = ObjectAnimator.ofFloat(this, RecentsView.CONTENT_ALPHA,1, 0);
+        ObjectAnimator tvFade = ObjectAnimator.ofFloat(this, RecentsView.CONTENT_ALPHA, 1, 0);
         pendingAnimation.add(tvFade, DECELERATE_2, SpringProperty.DEFAULT);
         pendingAnimation.buildAnim().start();
         return true;
@@ -5142,7 +5167,7 @@
         if (mShowEmptyMessage && hasValidSize && mEmptyTextLayout == null) {
             int availableWidth = mLastMeasureSize.x - mEmptyMessagePadding - mEmptyMessagePadding;
             mEmptyTextLayout = StaticLayout.Builder.obtain(mEmptyMessage, 0, mEmptyMessage.length(),
-                    mEmptyMessagePaint, availableWidth)
+                            mEmptyMessagePaint, availableWidth)
                     .setAlignment(Layout.Alignment.ALIGN_CENTER)
                     .build();
             int totalHeight = mEmptyTextLayout.getHeight()
@@ -5269,7 +5294,7 @@
         updateScrollSynchronously();
 
         int targetSysUiFlags = tv.getTaskContainers().getFirst().getSysUiStatusNavFlags();
-        final boolean[] passedOverviewThreshold = new boolean[] {false};
+        final boolean[] passedOverviewThreshold = new boolean[]{false};
         ValueAnimator progressAnim = ValueAnimator.ofFloat(0, 1);
         progressAnim.addUpdateListener(animator -> {
             // Once we pass a certain threshold, update the sysui flags to match the target
@@ -5390,7 +5415,7 @@
                     fullyVisibleTaskIds.addAll(taskIds);
                 }
             }
-            mRecentsViewData.getSettledFullyVisibleTaskIds().setValue(fullyVisibleTaskIds);
+            mRecentsViewModel.updateTasksFullyVisible(fullyVisibleTaskIds);
         }
     }
 
@@ -5789,6 +5814,7 @@
      * Sets whether or not we should clamp the scroll offset.
      * This is used to avoid x-axis movement when swiping up transient taskbar.
      * Should only be set at the beginning and end of the gesture, otherwise a jump may occur.
+     *
      * @param clampScrollOffset When true, we clamp the scroll to 0 before the clamp threshold is
      *                          met.
      */
@@ -5821,11 +5847,11 @@
         // Don't dampen the scroll (due to overscroll) if the adjacent tasks are offscreen, so that
         // the page can move freely given there's no visual indication why it shouldn't.
         int overScrollShift = mAdjacentPageHorizontalOffset > 0
-                        ? (int) Utilities.mapRange(
-                                mAdjacentPageHorizontalOffset,
-                                getOverScrollShift(),
-                                getUndampedOverScrollShift())
-                        : getOverScrollShift();
+                ? (int) Utilities.mapRange(
+                mAdjacentPageHorizontalOffset,
+                getOverScrollShift(),
+                getUndampedOverScrollShift())
+                : getOverScrollShift();
         return getScrollForPage(pageIndex) - getPagedOrientationHandler().getPrimaryScroll(this)
                 + overScrollShift + getOffsetFromScrollPosition(pageIndex);
     }
@@ -5942,7 +5968,10 @@
         if (mOverlayEnabled != overlayEnabled) {
             mOverlayEnabled = overlayEnabled;
             updateEnabledOverlays();
-            mRecentsViewData.getOverlayEnabled().setValue(overlayEnabled);
+
+            if (enableRefactorTaskThumbnail()) {
+                mRecentsViewModel.setOverlayEnabled(overlayEnabled);
+            }
         }
     }
 
@@ -6130,7 +6159,7 @@
     public boolean showAsGrid() {
         return mOverviewGridEnabled || (mCurrentGestureEndTarget != null
                 && mSizeStrategy.stateFromGestureEndTarget(mCurrentGestureEndTarget)
-                    .displayOverviewTasksAsGrid(mContainer.getDeviceProfile()));
+                .displayOverviewTasksAsGrid(mContainer.getDeviceProfile()));
     }
 
     private boolean showAsFullscreen() {
@@ -6171,7 +6200,7 @@
 
     /**
      * @return Corner radius in pixel value for PiP window, which is updated via
-     *         {@link #mIPipAnimationListener}
+     * {@link #mIPipAnimationListener}
      */
     public int getPipCornerRadius() {
         return mPipCornerRadius;
@@ -6179,7 +6208,7 @@
 
     /**
      * @return Shadow radius in pixel value for PiP window, which is updated via
-     *         {@link #mIPipAnimationListener}
+     * {@link #mIPipAnimationListener}
      */
     public int getPipShadowRadius() {
         return mPipShadowRadius;
diff --git a/quickstep/src/com/android/quickstep/views/TaskContainer.kt b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
index 74d120f..79725c6 100644
--- a/quickstep/src/com/android/quickstep/views/TaskContainer.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
@@ -29,10 +29,15 @@
 import com.android.launcher3.util.TransformingTouchDelegate
 import com.android.quickstep.TaskOverlayFactory
 import com.android.quickstep.TaskUtils
+import com.android.quickstep.recents.di.RecentsDependencies
+import com.android.quickstep.recents.di.get
+import com.android.quickstep.recents.di.getScope
+import com.android.quickstep.recents.di.inject
+import com.android.quickstep.recents.usecase.GetThumbnailUseCase
 import com.android.quickstep.task.thumbnail.TaskThumbnail
 import com.android.quickstep.task.thumbnail.TaskThumbnailView
-import com.android.quickstep.task.util.GetThumbnailUseCase
 import com.android.quickstep.task.viewmodel.TaskContainerData
+import com.android.quickstep.task.viewmodel.TaskThumbnailViewModel
 import com.android.systemui.shared.recents.model.Task
 
 /** Holder for all Task dependent information. */
@@ -55,21 +60,22 @@
     taskOverlayFactory: TaskOverlayFactory
 ) {
     val overlay: TaskOverlayFactory.TaskOverlay<*> = taskOverlayFactory.createOverlay(this)
-    val taskContainerData = TaskContainerData()
-
-    private val getThumbnailUseCase by lazy {
-        // TODO(b/335649589): Ideally create and obtain this from DI.
-        val recentsView =
-            RecentsViewContainer.containerFromContext<RecentsViewContainer>(
-                    overlay.taskView.context
-                )
-                .getOverviewPanel<RecentsView<*, *>>()
-        GetThumbnailUseCase(recentsView.mTasksRepository!!)
-    }
+    lateinit var taskContainerData: TaskContainerData
+    private val getThumbnailUseCase: GetThumbnailUseCase by RecentsDependencies.inject()
+    private val taskThumbnailViewModel: TaskThumbnailViewModel by
+        RecentsDependencies.inject(snapshotView)
 
     init {
         if (enableRefactorTaskThumbnail()) {
             require(snapshotView is TaskThumbnailView)
+            taskContainerData = RecentsDependencies.get(this)
+            RecentsDependencies.getScope(snapshotView).apply {
+                val taskViewScope = RecentsDependencies.getScope(taskView)
+                linkTo(taskViewScope)
+
+                val taskContainerScope = RecentsDependencies.getScope(this)
+                linkTo(taskContainerScope)
+            }
         } else {
             require(snapshotView is TaskThumbnailViewDeprecated)
         }
@@ -146,12 +152,10 @@
         overlay.destroy()
     }
 
-    // TODO(b/335649589): TaskView's VM will already have access to TaskThumbnailView's VM
-    //  so there will be no need to access TaskThumbnailView's VM through the TaskThumbnailView
     fun bindThumbnailView() {
         // TODO(b/343364498): Existing view has shouldShowScreenshot as an override as well but
         //  this should be decided inside TaskThumbnailViewModel.
-        thumbnailView.viewModel.bind(TaskThumbnail(task.key.id, taskView.isRunningTask))
+        taskThumbnailViewModel.bind(TaskThumbnail(task.key.id, taskView.isRunningTask))
     }
 
     fun setOverlayEnabled(enabled: Boolean) {
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index 004003c..f2f036a 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -80,8 +80,10 @@
 import com.android.quickstep.TaskOverlayFactory
 import com.android.quickstep.TaskViewUtils
 import com.android.quickstep.orientation.RecentsPagedOrientationHandler
+import com.android.quickstep.recents.di.RecentsDependencies
+import com.android.quickstep.recents.di.get
 import com.android.quickstep.task.thumbnail.TaskThumbnailView
-import com.android.quickstep.task.viewmodel.TaskViewData
+import com.android.quickstep.task.viewmodel.TaskViewModel
 import com.android.quickstep.util.ActiveGestureErrorDetector
 import com.android.quickstep.util.ActiveGestureLog
 import com.android.quickstep.util.BorderAnimator
@@ -115,7 +117,8 @@
     @IntDef(FLAG_UPDATE_ALL, FLAG_UPDATE_ICON, FLAG_UPDATE_THUMBNAIL, FLAG_UPDATE_CORNER_RADIUS)
     annotation class TaskDataChanges
 
-    val taskViewData = TaskViewData(type)
+    private lateinit var taskViewModel: TaskViewModel
+
     val taskIds: IntArray
         /** Returns a copy of integer array containing taskIds of all tasks in the TaskView. */
         get() = taskContainers.map { it.task.key.id }.toIntArray()
@@ -441,6 +444,11 @@
 
     init {
         setOnClickListener { _ -> onClick() }
+
+        if (enableRefactorTaskThumbnail()) {
+            taskViewModel = RecentsDependencies.get(this, "TaskViewType" to type)
+        }
+
         val keyboardFocusHighlightEnabled =
             (ENABLE_KEYBOARD_QUICK_SWITCH.get() || enableFocusOutline())
         val cursorHoverStatesEnabled = enableCursorHoverStates()
@@ -638,6 +646,7 @@
         orientedState: RecentsOrientedState,
         taskOverlayFactory: TaskOverlayFactory
     ) {
+
         cancelPendingLoadTasks()
         taskContainers =
             listOf(
@@ -1404,7 +1413,7 @@
         scaleX = scale
         scaleY = scale
         if (enableRefactorTaskThumbnail()) {
-            taskViewData.scale.value = scale
+            taskViewModel.updateScale(scale)
         }
         updateSnapshotRadius()
     }
@@ -1483,8 +1492,6 @@
 
     /** Updates [TaskThumbnailView] to reflect the latest [Task] state (i.e., task isRunning). */
     fun notifyIsRunningTaskUpdated() {
-        // TODO(b/335649589): TaskView's VM will already have access to TaskThumbnailView's VM
-        //  so there will be no need to access TaskThumbnailView's VM through the TaskThumbnailView
         taskContainers.forEach { it.bindThumbnailView() }
     }
 
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/BubbleViewTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/BubbleViewTest.kt
new file mode 100644
index 0000000..8bad3b9
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/BubbleViewTest.kt
@@ -0,0 +1,76 @@
+/*
+ * 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.taskbar.bubbles
+
+import android.content.Context
+import android.graphics.Color
+import android.graphics.Path
+import android.graphics.drawable.ColorDrawable
+import android.view.LayoutInflater
+import androidx.core.graphics.drawable.toBitmap
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.R
+import com.android.wm.shell.common.bubbles.BubbleInfo
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BubbleViewTest {
+
+    private val context = ApplicationProvider.getApplicationContext<Context>()
+    private lateinit var bubbleView: BubbleView
+    private lateinit var overflowView: BubbleView
+    private lateinit var bubble: BubbleBarBubble
+
+    @Test
+    fun hasUnseenContent_bubble() {
+        setupBubbleViews()
+        assertThat(bubbleView.hasUnseenContent()).isTrue()
+
+        bubbleView.markSeen()
+        assertThat(bubbleView.hasUnseenContent()).isFalse()
+    }
+
+    @Test
+    fun hasUnseenContent_overflow() {
+        setupBubbleViews()
+        assertThat(overflowView.hasUnseenContent()).isFalse()
+    }
+
+    private fun setupBubbleViews() {
+        InstrumentationRegistry.getInstrumentation().runOnMainSync {
+            val inflater = LayoutInflater.from(context)
+
+            val bitmap = ColorDrawable(Color.WHITE).toBitmap(width = 20, height = 20)
+            overflowView = inflater.inflate(R.layout.bubblebar_item_view, null, false) as BubbleView
+            overflowView.setOverflow(BubbleBarOverflow(overflowView), bitmap)
+
+            val bubbleInfo =
+                BubbleInfo("key", 0, null, null, 0, context.packageName, null, null, false)
+            bubbleView = inflater.inflate(R.layout.bubblebar_item_view, null, false) as BubbleView
+            bubble =
+                BubbleBarBubble(bubbleInfo, bubbleView, bitmap, bitmap, Color.WHITE, Path(), "")
+            bubbleView.setBubble(bubble)
+        }
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+    }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherRestoreEventLoggerImplTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherRestoreEventLoggerImplTest.kt
new file mode 100644
index 0000000..24f9696
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherRestoreEventLoggerImplTest.kt
@@ -0,0 +1,139 @@
+package com.android.quickstep
+
+/*
+ * 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.
+ */
+
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.Flags
+import com.android.launcher3.LauncherSettings.Favorites
+import com.android.launcher3.util.LauncherModelHelper
+import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext
+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
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(Flags.FLAG_ENABLE_LAUNCHER_BR_METRICS_FIXED)
+class LauncherRestoreEventLoggerImplTest {
+
+    @get:Rule val setFlagsRule = SetFlagsRule()
+
+    private val mLauncherModelHelper = LauncherModelHelper()
+    private val mSandboxContext: SandboxModelContext = mLauncherModelHelper.sandboxContext
+    private lateinit var loggerUnderTest: LauncherRestoreEventLoggerImpl
+
+    @Before
+    fun setup() {
+        loggerUnderTest = LauncherRestoreEventLoggerImpl(mSandboxContext)
+    }
+
+    @After
+    fun teardown() {
+        loggerUnderTest.restoreEventLogger.clearData()
+        mLauncherModelHelper.destroy()
+    }
+
+    @Test
+    fun `logLauncherItemsRestoreFailed logs multiple items as failing restore`() {
+        // Given
+        val expectedDataType = "application"
+        val expectedError = "test_failure"
+        // When
+        loggerUnderTest.logLauncherItemsRestoreFailed(
+            dataType = expectedDataType,
+            count = 5,
+            error = expectedError
+        )
+        // Then
+        val actualResult = loggerUnderTest.restoreEventLogger.loggingResults.first()
+        assertThat(actualResult.dataType).isEqualTo(expectedDataType)
+        assertThat(actualResult.successCount).isEqualTo(0)
+        assertThat(actualResult.failCount).isEqualTo(5)
+        assertThat(actualResult.errors.keys).containsExactly(expectedError)
+    }
+
+    @Test
+    fun `logLauncherItemsRestored logs multiple items as restored`() {
+        // Given
+        val expectedDataType = "application"
+        // When
+        loggerUnderTest.logLauncherItemsRestored(dataType = expectedDataType, count = 5)
+        // Then
+        val actualResult = loggerUnderTest.restoreEventLogger.loggingResults.first()
+        assertThat(actualResult.dataType).isEqualTo(expectedDataType)
+        assertThat(actualResult.successCount).isEqualTo(5)
+        assertThat(actualResult.failCount).isEqualTo(0)
+        assertThat(actualResult.errors.keys).isEmpty()
+    }
+
+    @Test
+    fun `logSingleFavoritesItemRestored logs a single Favorites Item as restored`() {
+        // Given
+        val expectedDataType = "widget"
+        // When
+        loggerUnderTest.logSingleFavoritesItemRestored(favoritesId = Favorites.ITEM_TYPE_APPWIDGET)
+        // Then
+        val actualResult = loggerUnderTest.restoreEventLogger.loggingResults.first()
+        assertThat(actualResult.dataType).isEqualTo(expectedDataType)
+        assertThat(actualResult.successCount).isEqualTo(1)
+        assertThat(actualResult.failCount).isEqualTo(0)
+        assertThat(actualResult.errors.keys).isEmpty()
+    }
+
+    @Test
+    fun `logSingleFavoritesItemRestoreFailed logs a single Favorites Item as failing restore`() {
+        // Given
+        val expectedDataType = "widget"
+        val expectedError = "test_failure"
+        // When
+        loggerUnderTest.logSingleFavoritesItemRestoreFailed(
+            favoritesId = Favorites.ITEM_TYPE_APPWIDGET,
+            error = expectedError
+        )
+        // Then
+        val actualResult = loggerUnderTest.restoreEventLogger.loggingResults.first()
+        assertThat(actualResult.dataType).isEqualTo(expectedDataType)
+        assertThat(actualResult.successCount).isEqualTo(0)
+        assertThat(actualResult.failCount).isEqualTo(1)
+        assertThat(actualResult.errors.keys).containsExactly(expectedError)
+    }
+
+    @Test
+    fun `logFavoritesItemsRestoreFailed logs multiple Favorites Items as failing restore`() {
+        // Given
+        val expectedDataType = "deep_shortcut"
+        val expectedError = "test_failure"
+        // When
+        loggerUnderTest.logFavoritesItemsRestoreFailed(
+            favoritesId = Favorites.ITEM_TYPE_DEEP_SHORTCUT,
+            count = 5,
+            error = expectedError
+        )
+        // Then
+        val actualResult = loggerUnderTest.restoreEventLogger.loggingResults.first()
+        assertThat(actualResult.dataType).isEqualTo(expectedDataType)
+        assertThat(actualResult.successCount).isEqualTo(0)
+        assertThat(actualResult.failCount).isEqualTo(5)
+        assertThat(actualResult.errors.keys).containsExactly(expectedError)
+    }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/logging/SettingsChangeLoggerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/logging/SettingsChangeLoggerTest.kt
new file mode 100644
index 0000000..070eeaf
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/logging/SettingsChangeLoggerTest.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.quickstep.logging
+
+import android.content.Context
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.launcher3.LauncherPrefs
+import com.android.launcher3.LauncherPrefs.Companion.backedUpItem
+import com.android.launcher3.logging.InstanceId
+import com.android.launcher3.logging.StatsLogManager
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.atLeastOnce
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@RunWith(AndroidJUnit4::class)
+class SettingsChangeLoggerTest {
+    private val mContext: Context = ApplicationProvider.getApplicationContext()
+
+    private val mInstanceId = InstanceId.fakeInstanceId(1)
+
+    private lateinit var mSystemUnderTest: SettingsChangeLogger
+
+    @Mock private lateinit var mStatsLogManager: StatsLogManager
+
+    @Mock private lateinit var mMockLogger: StatsLogManager.StatsLogger
+
+    @Captor private lateinit var mEventCaptor: ArgumentCaptor<StatsLogManager.EventEnum>
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        whenever(mStatsLogManager.logger()).doReturn(mMockLogger)
+        whenever(mStatsLogManager.logger().withInstanceId(any())).doReturn(mMockLogger)
+
+        mSystemUnderTest = SettingsChangeLogger(mContext, mStatsLogManager)
+    }
+
+    @After
+    fun tearDown() {
+        mSystemUnderTest.close()
+    }
+
+    @Test
+    fun loggingPrefs_correctDefaultValue() {
+        assertThat(mSystemUnderTest.loggingPrefs["pref_allowRotation"]!!.defaultValue).isFalse()
+        assertThat(mSystemUnderTest.loggingPrefs["pref_add_icon_to_home"]!!.defaultValue).isTrue()
+        assertThat(mSystemUnderTest.loggingPrefs["pref_overview_action_suggestions"]!!.defaultValue)
+            .isTrue()
+        assertThat(mSystemUnderTest.loggingPrefs["pref_smartspace_home_screen"]!!.defaultValue)
+            .isTrue()
+        assertThat(mSystemUnderTest.loggingPrefs["pref_enable_minus_one"]!!.defaultValue).isTrue()
+    }
+
+    @Test
+    fun logSnapshot_defaultValue() {
+        mSystemUnderTest.logSnapshot(mInstanceId)
+
+        verify(mMockLogger, atLeastOnce()).log(mEventCaptor.capture())
+        val capturedEvents = mEventCaptor.allValues
+        assertThat(capturedEvents.isNotEmpty()).isTrue()
+        verifyDefaultEvent(capturedEvents)
+        // pref_allowRotation false
+        assertThat(capturedEvents.any { it.id == 616 }).isTrue()
+    }
+
+    @Test
+    fun logSnapshot_updateValue() {
+        LauncherPrefs.get(mContext)
+            .put(
+                item =
+                    backedUpItem(
+                        sharedPrefKey = "pref_allowRotation",
+                        defaultValue = false,
+                    ),
+                value = true
+            )
+
+        mSystemUnderTest.logSnapshot(mInstanceId)
+
+        verify(mMockLogger, atLeastOnce()).log(mEventCaptor.capture())
+        val capturedEvents = mEventCaptor.allValues
+        assertThat(capturedEvents.isNotEmpty()).isTrue()
+        verifyDefaultEvent(capturedEvents)
+        // pref_allowRotation true
+        assertThat(capturedEvents.any { it.id == 615 }).isTrue()
+    }
+
+    private fun verifyDefaultEvent(capturedEvents: MutableList<StatsLogManager.EventEnum>) {
+        // LAUNCHER_NOTIFICATION_DOT_ENABLED
+        assertThat(capturedEvents.any { it.id == 611 }).isTrue()
+        // LAUNCHER_NAVIGATION_MODE_GESTURE_BUTTON
+        assertThat(capturedEvents.any { it.id == 625 }).isTrue()
+        // LAUNCHER_THEMED_ICON_DISABLED
+        assertThat(capturedEvents.any { it.id == 837 }).isTrue()
+        // pref_add_icon_to_home true
+        assertThat(capturedEvents.any { it.id == 613 }).isTrue()
+        // pref_overview_action_suggestions true
+        assertThat(capturedEvents.any { it.id == 619 }).isTrue()
+        // pref_smartspace_home_screen true
+        assertThat(capturedEvents.any { it.id == 621 }).isTrue()
+        // pref_enable_minus_one true
+        assertThat(capturedEvents.any { it.id == 617 }).isTrue()
+    }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/usecase/GetThumbnailPositionUseCaseTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/usecase/GetThumbnailPositionUseCaseTest.kt
new file mode 100644
index 0000000..e657d59
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/usecase/GetThumbnailPositionUseCaseTest.kt
@@ -0,0 +1,134 @@
+/*
+ * 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.usecase
+
+import android.content.ComponentName
+import android.content.Intent
+import android.graphics.Bitmap
+import android.graphics.Color
+import android.graphics.Matrix
+import android.graphics.Rect
+import android.view.Surface.ROTATION_90
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.quickstep.recents.data.FakeTasksRepository
+import com.android.quickstep.recents.data.RecentsDeviceProfileRepository
+import com.android.quickstep.recents.data.RecentsRotationStateRepository
+import com.android.quickstep.recents.usecase.ThumbnailPositionState.MatrixScaling
+import com.android.quickstep.recents.usecase.ThumbnailPositionState.MissingThumbnail
+import com.android.systemui.shared.recents.model.Task
+import com.android.systemui.shared.recents.model.ThumbnailData
+import com.android.systemui.shared.recents.utilities.PreviewPositionHelper
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+/** Test for [GetThumbnailPositionUseCase] */
+@RunWith(AndroidJUnit4::class)
+class GetThumbnailPositionUseCaseTest {
+    private val task =
+        Task(Task.TaskKey(TASK_ID, 0, Intent(), ComponentName("", ""), 0, 2000)).apply {
+            colorBackground = Color.BLACK
+        }
+    private val thumbnailData =
+        ThumbnailData(
+            thumbnail =
+                mock<Bitmap>().apply {
+                    whenever(width).thenReturn(THUMBNAIL_WIDTH)
+                    whenever(height).thenReturn(THUMBNAIL_HEIGHT)
+                }
+        )
+
+    private val deviceProfileRepository = mock<RecentsDeviceProfileRepository>()
+    private val rotationStateRepository = mock<RecentsRotationStateRepository>()
+    private val tasksRepository = FakeTasksRepository()
+    private val previewPositionHelper = mock<PreviewPositionHelper>()
+
+    private val systemUnderTest =
+        GetThumbnailPositionUseCase(
+            deviceProfileRepository,
+            rotationStateRepository,
+            tasksRepository,
+            previewPositionHelper
+        )
+
+    @Test
+    fun invisibleTask_returnsIdentityMatrix() = runTest {
+        tasksRepository.seedTasks(listOf(task))
+
+        assertThat(systemUnderTest.run(TASK_ID, CANVAS_WIDTH, CANVAS_HEIGHT, isRtl = true))
+            .isInstanceOf(MissingThumbnail::class.java)
+    }
+
+    @Test
+    fun visibleTaskWithoutThumbnailData_returnsIdentityMatrix() = runTest {
+        tasksRepository.seedTasks(listOf(task))
+        tasksRepository.setVisibleTasks(listOf(TASK_ID))
+
+        assertThat(systemUnderTest.run(TASK_ID, CANVAS_WIDTH, CANVAS_HEIGHT, isRtl = true))
+            .isInstanceOf(MissingThumbnail::class.java)
+    }
+
+    @Test
+    fun visibleTaskWithThumbnailData_returnsTransformedMatrix() = runTest {
+        tasksRepository.seedThumbnailData(mapOf(TASK_ID to thumbnailData))
+        tasksRepository.seedTasks(listOf(task))
+        tasksRepository.setVisibleTasks(listOf(TASK_ID))
+
+        val isLargeScreen = true
+        val activityRotation = ROTATION_90
+        val isRtl = true
+        val isRotated = true
+
+        whenever(deviceProfileRepository.getRecentsDeviceProfile())
+            .thenReturn(RecentsDeviceProfileRepository.RecentsDeviceProfile(isLargeScreen))
+        whenever(rotationStateRepository.getRecentsRotationState())
+            .thenReturn(RecentsRotationStateRepository.RecentsRotationState(activityRotation))
+
+        whenever(previewPositionHelper.matrix).thenReturn(MATRIX)
+        whenever(previewPositionHelper.isOrientationChanged).thenReturn(isRotated)
+
+        assertThat(systemUnderTest.run(TASK_ID, CANVAS_WIDTH, CANVAS_HEIGHT, isRtl))
+            .isEqualTo(MatrixScaling(MATRIX, isRotated))
+
+        verify(previewPositionHelper)
+            .updateThumbnailMatrix(
+                Rect(0, 0, THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT),
+                thumbnailData,
+                CANVAS_WIDTH,
+                CANVAS_HEIGHT,
+                isLargeScreen,
+                activityRotation,
+                isRtl
+            )
+    }
+
+    companion object {
+        const val TASK_ID = 2
+        const val THUMBNAIL_WIDTH = 100
+        const val THUMBNAIL_HEIGHT = 200
+        const val CANVAS_WIDTH = 300
+        const val CANVAS_HEIGHT = 600
+        val MATRIX =
+            Matrix().apply {
+                setValues(floatArrayOf(2.3f, 4.5f, 2.6f, 7.4f, 3.4f, 2.3f, 2.5f, 6.0f, 3.4f))
+            }
+    }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/util/GetThumbnailUseCaseTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/usecase/GetThumbnailUseCaseTest.kt
similarity index 98%
rename from quickstep/tests/multivalentTests/src/com/android/quickstep/task/util/GetThumbnailUseCaseTest.kt
rename to quickstep/tests/multivalentTests/src/com/android/quickstep/recents/usecase/GetThumbnailUseCaseTest.kt
index 414f8ca..12a94cf 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/util/GetThumbnailUseCaseTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/usecase/GetThumbnailUseCaseTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.quickstep.task.util
+package com.android.quickstep.recents.usecase
 
 import android.content.ComponentName
 import android.content.Intent
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelTest.kt
index b78f871..754c9d1 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelTest.kt
@@ -20,15 +20,19 @@
 import android.content.Intent
 import android.graphics.Bitmap
 import android.graphics.Color
-import android.graphics.Rect
+import android.graphics.Matrix
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.quickstep.recents.data.FakeTasksRepository
+import com.android.quickstep.recents.usecase.GetThumbnailPositionUseCase
+import com.android.quickstep.recents.usecase.ThumbnailPositionState.MatrixScaling
+import com.android.quickstep.recents.usecase.ThumbnailPositionState.MissingThumbnail
 import com.android.quickstep.recents.viewmodel.RecentsViewData
 import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.BackgroundOnly
 import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.LiveTile
 import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Snapshot
 import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized
 import com.android.quickstep.task.viewmodel.TaskContainerData
+import com.android.quickstep.task.viewmodel.TaskThumbnailViewModel
 import com.android.quickstep.task.viewmodel.TaskViewData
 import com.android.quickstep.views.TaskViewType
 import com.android.systemui.shared.recents.model.Task
@@ -48,8 +52,15 @@
     private val taskViewData by lazy { TaskViewData(taskViewType) }
     private val taskContainerData = TaskContainerData()
     private val tasksRepository = FakeTasksRepository()
+    private val mGetThumbnailPositionUseCase = mock<GetThumbnailPositionUseCase>()
     private val systemUnderTest by lazy {
-        TaskThumbnailViewModel(recentsViewData, taskViewData, taskContainerData, tasksRepository)
+        TaskThumbnailViewModel(
+            recentsViewData,
+            taskViewData,
+            taskContainerData,
+            tasksRepository,
+            mGetThumbnailPositionUseCase
+        )
     }
 
     private val tasks = (0..5).map(::createTaskWithId)
@@ -149,7 +160,6 @@
                 Snapshot(
                     backgroundColor = Color.rgb(2, 2, 2),
                     bitmap = expectedThumbnailData.thumbnail!!,
-                    drawnRect = Rect(0, 0, THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT)
                 )
             )
     }
@@ -170,11 +180,38 @@
                 Snapshot(
                     backgroundColor = Color.rgb(2, 2, 2),
                     bitmap = expectedThumbnailData.thumbnail!!,
-                    drawnRect = Rect(0, 0, THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT)
                 )
             )
     }
 
+    @Test
+    fun getSnapshotMatrix_MissingThumbnail() = runTest {
+        val taskId = 2
+        val recentTask = TaskThumbnail(taskId = taskId, isRunning = false)
+        val isRtl = true
+
+        whenever(mGetThumbnailPositionUseCase.run(taskId, CANVAS_WIDTH, CANVAS_HEIGHT, isRtl))
+            .thenReturn(MissingThumbnail)
+
+        systemUnderTest.bind(recentTask)
+        assertThat(systemUnderTest.getThumbnailPositionState(CANVAS_WIDTH, CANVAS_HEIGHT, isRtl))
+            .isEqualTo(Matrix.IDENTITY_MATRIX)
+    }
+
+    @Test
+    fun getSnapshotMatrix_MatrixScaling() = runTest {
+        val taskId = 2
+        val recentTask = TaskThumbnail(taskId = taskId, isRunning = false)
+        val isRtl = true
+
+        whenever(mGetThumbnailPositionUseCase.run(taskId, CANVAS_WIDTH, CANVAS_HEIGHT, isRtl))
+            .thenReturn(MatrixScaling(MATRIX, isRotated = false))
+
+        systemUnderTest.bind(recentTask)
+        assertThat(systemUnderTest.getThumbnailPositionState(CANVAS_WIDTH, CANVAS_HEIGHT, isRtl))
+            .isEqualTo(MATRIX)
+    }
+
     private fun createTaskWithId(taskId: Int) =
         Task(Task.TaskKey(taskId, 0, Intent(), ComponentName("", ""), 0, 2000)).apply {
             colorBackground = Color.argb(taskId, taskId, taskId, taskId)
@@ -191,5 +228,11 @@
     companion object {
         const val THUMBNAIL_WIDTH = 100
         const val THUMBNAIL_HEIGHT = 200
+        const val CANVAS_WIDTH = 300
+        const val CANVAS_HEIGHT = 600
+        val MATRIX =
+            Matrix().apply {
+                setValues(floatArrayOf(2.3f, 4.5f, 2.6f, 7.4f, 3.4f, 2.3f, 2.5f, 6.0f, 3.4f))
+            }
     }
 }
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModelTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModelTest.kt
index 40482c4..d0887df 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModelTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModelTest.kt
@@ -23,9 +23,13 @@
 import android.graphics.Matrix
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.quickstep.recents.data.FakeTasksRepository
+import com.android.quickstep.recents.usecase.GetThumbnailPositionUseCase
+import com.android.quickstep.recents.usecase.ThumbnailPositionState.MatrixScaling
+import com.android.quickstep.recents.usecase.ThumbnailPositionState.MissingThumbnail
 import com.android.quickstep.recents.viewmodel.RecentsViewData
 import com.android.quickstep.task.thumbnail.TaskOverlayUiState.Disabled
 import com.android.quickstep.task.thumbnail.TaskOverlayUiState.Enabled
+import com.android.quickstep.task.viewmodel.TaskOverlayViewModel.ThumbnailPositionState
 import com.android.systemui.shared.recents.model.Task
 import com.android.systemui.shared.recents.model.ThumbnailData
 import com.google.common.truth.Truth.assertThat
@@ -53,7 +57,9 @@
         )
     private val recentsViewData = RecentsViewData()
     private val tasksRepository = FakeTasksRepository()
-    private val systemUnderTest = TaskOverlayViewModel(task, recentsViewData, tasksRepository)
+    private val mGetThumbnailPositionUseCase = mock<GetThumbnailPositionUseCase>()
+    private val systemUnderTest =
+        TaskOverlayViewModel(task, recentsViewData, mGetThumbnailPositionUseCase, tasksRepository)
 
     @Test
     fun initialStateIsDisabled() = runTest {
@@ -87,7 +93,6 @@
                 Enabled(
                     isRealSnapshot = false,
                     thumbnail = null,
-                    thumbnailMatrix = Matrix.IDENTITY_MATRIX
                 )
             )
     }
@@ -107,7 +112,6 @@
                 Enabled(
                     isRealSnapshot = true,
                     thumbnail = thumbnailData.thumbnail,
-                    thumbnailMatrix = Matrix.IDENTITY_MATRIX
                 )
             )
     }
@@ -127,7 +131,6 @@
                 Enabled(
                     isRealSnapshot = false,
                     thumbnail = thumbnailData.thumbnail,
-                    thumbnailMatrix = Matrix.IDENTITY_MATRIX
                 )
             )
     }
@@ -147,14 +150,42 @@
                 Enabled(
                     isRealSnapshot = false,
                     thumbnail = thumbnailData.thumbnail,
-                    thumbnailMatrix = Matrix.IDENTITY_MATRIX
                 )
             )
     }
 
+    @Test
+    fun getThumbnailMatrix_MissingThumbnail() = runTest {
+        val isRtl = true
+
+        whenever(mGetThumbnailPositionUseCase.run(TASK_ID, CANVAS_WIDTH, CANVAS_HEIGHT, isRtl))
+            .thenReturn(MissingThumbnail)
+
+        assertThat(systemUnderTest.getThumbnailPositionState(CANVAS_WIDTH, CANVAS_HEIGHT, isRtl))
+            .isEqualTo(ThumbnailPositionState(Matrix.IDENTITY_MATRIX, isRotated = false))
+    }
+
+    @Test
+    fun getThumbnailMatrix_MatrixScaling() = runTest {
+        val isRtl = true
+        val isRotated = true
+
+        whenever(mGetThumbnailPositionUseCase.run(TASK_ID, CANVAS_WIDTH, CANVAS_HEIGHT, isRtl))
+            .thenReturn(MatrixScaling(MATRIX, isRotated))
+
+        assertThat(systemUnderTest.getThumbnailPositionState(CANVAS_WIDTH, CANVAS_HEIGHT, isRtl))
+            .isEqualTo(ThumbnailPositionState(MATRIX, isRotated))
+    }
+
     companion object {
         const val TASK_ID = 0
         const val THUMBNAIL_WIDTH = 100
         const val THUMBNAIL_HEIGHT = 200
+        const val CANVAS_WIDTH = 300
+        const val CANVAS_HEIGHT = 600
+        val MATRIX =
+            Matrix().apply {
+                setValues(floatArrayOf(2.3f, 4.5f, 2.6f, 7.4f, 3.4f, 2.3f, 2.5f, 6.0f, 3.4f))
+            }
     }
 }
diff --git a/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java b/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java
index 5d00255..d049fbc 100644
--- a/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java
+++ b/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java
@@ -16,6 +16,8 @@
 
 package com.android.quickstep;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static junit.framework.TestCase.assertNull;
 
 import static org.junit.Assert.assertEquals;
@@ -69,14 +71,14 @@
     }
 
     @Test
-    public void onRecentTasksChanged_doesNotFetchTasks() {
+    public void onRecentTasksChanged_doesNotFetchTasks() throws Exception {
         mRecentTasksList.onRecentTasksChanged();
         verify(mockSystemUiProxy, times(0))
                 .getRecentTasks(anyInt(), anyInt());
     }
 
     @Test
-    public void loadTasksInBackground_onlyKeys_noValidTaskDescription() {
+    public void loadTasksInBackground_onlyKeys_noValidTaskDescription() throws Exception  {
         GroupedRecentTaskInfo recentTaskInfos = GroupedRecentTaskInfo.forSplitTasks(
                 new ActivityManager.RecentTaskInfo(), new ActivityManager.RecentTaskInfo(), null);
         when(mockSystemUiProxy.getRecentTasks(anyInt(), anyInt()))
@@ -91,7 +93,19 @@
     }
 
     @Test
-    public void loadTasksInBackground_moreThanKeys_hasValidTaskDescription() {
+    public void loadTasksInBackground_GetRecentTasksException() throws Exception  {
+        when(mockSystemUiProxy.getRecentTasks(anyInt(), anyInt()))
+                .thenThrow(new SystemUiProxy.GetRecentTasksException("task load failed"));
+
+        RecentTasksList.TaskLoadResult taskList = mRecentTasksList.loadTasksInBackground(
+                Integer.MAX_VALUE, -1, false);
+
+        assertThat(taskList.mRequestId).isEqualTo(-1);
+        assertThat(taskList).isEmpty();
+    }
+
+    @Test
+    public void loadTasksInBackground_moreThanKeys_hasValidTaskDescription() throws Exception  {
         String taskDescription = "Wheeee!";
         ActivityManager.RecentTaskInfo task1 = new ActivityManager.RecentTaskInfo();
         task1.taskDescription = new ActivityManager.TaskDescription(taskDescription);
@@ -111,7 +125,7 @@
     }
 
     @Test
-    public void loadTasksInBackground_freeformTask_createsDesktopTask() {
+    public void loadTasksInBackground_freeformTask_createsDesktopTask() throws Exception  {
         ActivityManager.RecentTaskInfo[] tasks = {
                 createRecentTaskInfo(1 /* taskId */),
                 createRecentTaskInfo(4 /* taskId */),
@@ -134,7 +148,8 @@
     }
 
     @Test
-    public void loadTasksInBackground_freeformTask_onlyMinimizedTasks_doesNotCreateDesktopTask() {
+    public void loadTasksInBackground_freeformTask_onlyMinimizedTasks_doesNotCreateDesktopTask()
+            throws Exception {
         ActivityManager.RecentTaskInfo[] tasks = {
                 createRecentTaskInfo(1 /* taskId */),
                 createRecentTaskInfo(4 /* taskId */),
diff --git a/res/color-night-v31/material_color_surface.xml b/res/color-night-v31/material_color_surface.xml
deleted file mode 100644
index a645f24..0000000
--- a/res/color-night-v31/material_color_surface.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2022 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:color="@android:color/system_neutral1_500" android:lStar="6" />
-</selector>
\ No newline at end of file
diff --git a/res/color-night-v31/material_color_surface_bright.xml b/res/color-night-v31/material_color_surface_bright.xml
deleted file mode 100644
index f34ed6c..0000000
--- a/res/color-night-v31/material_color_surface_bright.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2022 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:color="@android:color/system_neutral1_500" android:lStar="24" />
-</selector>
\ No newline at end of file
diff --git a/res/color-night-v31/material_color_surface_container.xml b/res/color-night-v31/material_color_surface_container.xml
deleted file mode 100644
index 002b88e..0000000
--- a/res/color-night-v31/material_color_surface_container.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2022 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:color="@android:color/system_neutral1_500" android:lStar="12" />
-</selector>
\ No newline at end of file
diff --git a/res/color-night-v31/material_color_surface_container_high.xml b/res/color-night-v31/material_color_surface_container_high.xml
deleted file mode 100644
index edd36fc..0000000
--- a/res/color-night-v31/material_color_surface_container_high.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2022 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:color="@android:color/system_neutral1_500" android:lStar="17" />
-</selector>
\ No newline at end of file
diff --git a/res/color-night-v31/material_color_surface_container_highest.xml b/res/color-night-v31/material_color_surface_container_highest.xml
deleted file mode 100644
index e54f953..0000000
--- a/res/color-night-v31/material_color_surface_container_highest.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2022 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:color="@android:color/system_neutral1_500" android:lStar="22" />
-</selector>
\ No newline at end of file
diff --git a/res/color-night-v31/material_color_surface_container_low.xml b/res/color-night-v31/material_color_surface_container_low.xml
deleted file mode 100644
index 40f0d4c..0000000
--- a/res/color-night-v31/material_color_surface_container_low.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2022 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:color="@android:color/system_neutral1_500" android:lStar="10" />
-</selector>
\ No newline at end of file
diff --git a/res/color-night-v31/material_color_surface_container_lowest.xml b/res/color-night-v31/material_color_surface_container_lowest.xml
deleted file mode 100644
index 24f559b..0000000
--- a/res/color-night-v31/material_color_surface_container_lowest.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2022 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:color="@android:color/system_neutral1_500" android:lStar="4" />
-</selector>
\ No newline at end of file
diff --git a/res/color-night-v31/material_color_surface_dim.xml b/res/color-night-v31/material_color_surface_dim.xml
deleted file mode 100644
index a645f24..0000000
--- a/res/color-night-v31/material_color_surface_dim.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2022 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:color="@android:color/system_neutral1_500" android:lStar="6" />
-</selector>
\ No newline at end of file
diff --git a/res/color-night-v31/material_color_surface_inverse.xml b/res/color-night-v31/material_color_surface_inverse.xml
deleted file mode 100644
index ac63072..0000000
--- a/res/color-night-v31/material_color_surface_inverse.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2022 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:color="@android:color/system_neutral1_500" android:lStar="98" />
-</selector>
\ No newline at end of file
diff --git a/res/color-night-v31/material_color_surface_variant.xml b/res/color-night-v31/material_color_surface_variant.xml
deleted file mode 100644
index a645f24..0000000
--- a/res/color-night-v31/material_color_surface_variant.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2022 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:color="@android:color/system_neutral1_500" android:lStar="6" />
-</selector>
\ No newline at end of file
diff --git a/res/color-night-v31/popup_shade_first.xml b/res/color-night-v31/popup_shade_first.xml
index 83822a6..28995e3 100644
--- a/res/color-night-v31/popup_shade_first.xml
+++ b/res/color-night-v31/popup_shade_first.xml
@@ -12,7 +12,6 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
-    <item android:color="?androidprv:attr/materialColorSurfaceContainer"/>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="?attr/materialColorSurfaceContainer"/>
 </selector>
diff --git a/res/color-v31/material_color_surface.xml b/res/color-v31/material_color_surface.xml
deleted file mode 100644
index b049851..0000000
--- a/res/color-v31/material_color_surface.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2023 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:color="@android:color/system_neutral1_500" android:lStar="98" />
-</selector>
\ No newline at end of file
diff --git a/res/color-v31/material_color_surface_bright.xml b/res/color-v31/material_color_surface_bright.xml
deleted file mode 100644
index b049851..0000000
--- a/res/color-v31/material_color_surface_bright.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2023 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:color="@android:color/system_neutral1_500" android:lStar="98" />
-</selector>
\ No newline at end of file
diff --git a/res/color-v31/material_color_surface_container.xml b/res/color-v31/material_color_surface_container.xml
deleted file mode 100644
index b031c08..0000000
--- a/res/color-v31/material_color_surface_container.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2023 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:color="@android:color/system_neutral1_500" android:lStar="94" />
-</selector>
\ No newline at end of file
diff --git a/res/color-v31/material_color_surface_container_high.xml b/res/color-v31/material_color_surface_container_high.xml
deleted file mode 100644
index a996d51..0000000
--- a/res/color-v31/material_color_surface_container_high.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2023 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:color="@android:color/system_neutral1_500" android:lStar="92" />
-</selector>
\ No newline at end of file
diff --git a/res/color-v31/material_color_surface_container_highest.xml b/res/color-v31/material_color_surface_container_highest.xml
deleted file mode 100644
index e7a535a..0000000
--- a/res/color-v31/material_color_surface_container_highest.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2023 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:color="@android:color/system_neutral1_500" android:lStar="90" />
-</selector>
\ No newline at end of file
diff --git a/res/color-v31/material_color_surface_container_low.xml b/res/color-v31/material_color_surface_container_low.xml
deleted file mode 100644
index b8fe01e..0000000
--- a/res/color-v31/material_color_surface_container_low.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2023 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:color="@android:color/system_neutral1_500" android:lStar="96" />
-</selector>
\ No newline at end of file
diff --git a/res/color-v31/material_color_surface_container_lowest.xml b/res/color-v31/material_color_surface_container_lowest.xml
deleted file mode 100644
index 25e8666..0000000
--- a/res/color-v31/material_color_surface_container_lowest.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2022 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:color="@android:color/system_neutral1_500" android:lStar="100" />
-</selector>
\ No newline at end of file
diff --git a/res/color-v31/material_color_surface_dim.xml b/res/color-v31/material_color_surface_dim.xml
deleted file mode 100644
index e2d226f..0000000
--- a/res/color-v31/material_color_surface_dim.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2022 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:color="@android:color/system_neutral1_500" android:lStar="87" />
-</selector>
\ No newline at end of file
diff --git a/res/color-v31/material_color_surface_inverse.xml b/res/color-v31/material_color_surface_inverse.xml
deleted file mode 100644
index e189862..0000000
--- a/res/color-v31/material_color_surface_inverse.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2023 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:color="@android:color/system_neutral1_500" android:lStar="6" />
-</selector>
\ No newline at end of file
diff --git a/res/color-v31/material_color_surface_variant.xml b/res/color-v31/material_color_surface_variant.xml
deleted file mode 100644
index e2d226f..0000000
--- a/res/color-v31/material_color_surface_variant.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2022 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:color="@android:color/system_neutral1_500" android:lStar="87" />
-</selector>
\ No newline at end of file
diff --git a/res/color-v31/popup_shade_first.xml b/res/color-v31/popup_shade_first.xml
index 1278bb4..be73698 100644
--- a/res/color-v31/popup_shade_first.xml
+++ b/res/color-v31/popup_shade_first.xml
@@ -13,7 +13,6 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
-    <item android:color="?androidprv:attr/materialColorSurfaceContainer"/>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="?attr/materialColorSurfaceContainer"/>
 </selector>
diff --git a/res/color/overview_button.xml b/res/color/overview_button.xml
index 1dd8da6..0b317bd 100644
--- a/res/color/overview_button.xml
+++ b/res/color/overview_button.xml
@@ -1,12 +1,11 @@
 <?xml version="1.0" encoding="utf-8"?>
-<selector xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item
         android:alpha="1"
-        android:color="?androidprv:attr/materialColorOnSurface"
+        android:color="?attr/materialColorOnSurface"
         android:state_enabled="true" />
     <item
         android:alpha="?android:disabledAlpha"
-        android:color="?androidprv:attr/materialColorOnSurface"
+        android:color="?attr/materialColorOnSurface"
         android:state_enabled="false" />
 </selector>
\ No newline at end of file
diff --git a/res/color/popup_shade_first.xml b/res/color/popup_shade_first.xml
index 1278bb4..be73698 100644
--- a/res/color/popup_shade_first.xml
+++ b/res/color/popup_shade_first.xml
@@ -13,7 +13,6 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
-    <item android:color="?androidprv:attr/materialColorSurfaceContainer"/>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="?attr/materialColorSurfaceContainer"/>
 </selector>
diff --git a/res/drawable/add_item_dialog_background.xml b/res/drawable/add_item_dialog_background.xml
index e279fa0..be4765a 100644
--- a/res/drawable/add_item_dialog_background.xml
+++ b/res/drawable/add_item_dialog_background.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
     android:shape="rectangle" >
-    <solid android:color="@color/material_color_surface_container_highest" />
+    <solid android:color="?attr/materialColorSurfaceContainerHighest" />
     <corners
         android:topLeftRadius="?android:attr/dialogCornerRadius"
         android:topRightRadius="?android:attr/dialogCornerRadius" />
diff --git a/res/drawable/all_apps_tabs_background.xml b/res/drawable/all_apps_tabs_background.xml
index 1e7cff2..62927af 100644
--- a/res/drawable/all_apps_tabs_background.xml
+++ b/res/drawable/all_apps_tabs_background.xml
@@ -30,7 +30,7 @@
                 android:state_selected="false">
                 <shape android:shape="rectangle">
                     <corners android:radius="@dimen/all_apps_header_pill_corner_radius" />
-                    <solid android:color="@color/material_color_surface_bright" />
+                    <solid android:color="?attr/materialColorSurfaceBright" />
                 </shape>
             </item>
 
@@ -39,7 +39,7 @@
                 android:state_selected="true">
                 <shape android:shape="rectangle">
                     <corners android:radius="@dimen/all_apps_header_pill_corner_radius" />
-                    <solid android:color="@color/material_color_primary" />
+                    <solid android:color="?attr/materialColorPrimary" />
                 </shape>
             </item>
         </selector>
diff --git a/res/drawable/bg_rounded_corner_bottom_sheet_handle.xml b/res/drawable/bg_rounded_corner_bottom_sheet_handle.xml
index 379e0e5..a19465d 100644
--- a/res/drawable/bg_rounded_corner_bottom_sheet_handle.xml
+++ b/res/drawable/bg_rounded_corner_bottom_sheet_handle.xml
@@ -15,8 +15,7 @@
 -->
 
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     android:shape="rectangle" >
-    <solid android:color="?androidprv:attr/materialColorOutlineVariant"/>
+    <solid android:color="?attr/materialColorOutlineVariant"/>
     <corners android:radius="@dimen/bottom_sheet_handle_corner_radius" />
 </shape>
diff --git a/res/drawable/button_top_rounded_bordered_ripple.xml b/res/drawable/button_top_rounded_bordered_ripple.xml
index f5b6886..13959f6 100644
--- a/res/drawable/button_top_rounded_bordered_ripple.xml
+++ b/res/drawable/button_top_rounded_bordered_ripple.xml
@@ -25,7 +25,7 @@
                     android:topRightRadius="12dp"
                     android:bottomLeftRadius="4dp"
                     android:bottomRightRadius="4dp"  />
-                <solid android:color="@color/material_color_surface_container_highest"/>
+                <solid android:color="?attr/materialColorSurfaceContainerHighest"/>
                 <stroke
                     android:width="2dp"
                     android:color="@color/button_bg"/>
diff --git a/res/drawable/ic_close_work_edu.xml b/res/drawable/ic_close_work_edu.xml
index f336eea..e4053e3 100644
--- a/res/drawable/ic_close_work_edu.xml
+++ b/res/drawable/ic_close_work_edu.xml
@@ -20,6 +20,6 @@
     android:viewportWidth="960"
     android:viewportHeight="960">
     <path
-        android:fillColor="@color/material_color_on_surface"
+        android:fillColor="?attr/materialColorOnSurface"
         android:pathData="M256,760L200,704L424,480L200,256L256,200L480,424L704,200L760,256L536,480L760,704L704,760L480,536L256,760Z"/>
 </vector>
diff --git a/res/drawable/ic_more_vert_dots.xml b/res/drawable/ic_more_vert_dots.xml
new file mode 100644
index 0000000..c4659f8
--- /dev/null
+++ b/res/drawable/ic_more_vert_dots.xml
@@ -0,0 +1,26 @@
+<!--
+  ~ 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?attr/colorControlNormal">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,16c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z" />
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_private_space_with_background.xml b/res/drawable/ic_private_space_with_background.xml
index cb37c9a..cc73f6d 100644
--- a/res/drawable/ic_private_space_with_background.xml
+++ b/res/drawable/ic_private_space_with_background.xml
@@ -13,14 +13,13 @@
      limitations under the License.
 -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     android:viewportWidth="48"
     android:viewportHeight="48"
     android:width="48dp"
     android:height="48dp">
     <path
         android:pathData="M48 24A24 24 0 0 1 0 24A24 24 0 0 1 48 24Z"
-        android:fillColor="?androidprv:attr/materialColorSurfaceContainerLowest" />
+        android:fillColor="?attr/materialColorSurfaceContainerLowest" />
     <path
         android:pathData="M24.0021 10.6641L13.3354 14.6641V22.7841C13.3354 29.5174 17.8821 35.7974 24.0021 37.3307C30.1221 35.7974 34.6688 29.5174 34.6688 22.7841V14.6641L24.0021 10.6641ZM32.0021 22.7841C32.0021 28.1174 28.6021 33.0507 24.0021 34.5574C19.4021 33.0507 16.0021 28.1307 16.0021 22.7841V16.5174L24.0021 13.5174L32.0021 16.5174V22.7841Z"
         android:fillType="evenOdd"
diff --git a/res/drawable/icon_menu_arrow_background.xml b/res/drawable/icon_menu_arrow_background.xml
index 2eb1dfc..6345c2b 100644
--- a/res/drawable/icon_menu_arrow_background.xml
+++ b/res/drawable/icon_menu_arrow_background.xml
@@ -15,14 +15,13 @@
      limitations under the License.
 -->
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     android:autoMirrored="true">
     <gradient
         android:type="linear"
         android:angle="0"
         android:startColor="#00000000"
         android:centerX="0.25"
-        android:centerColor="?androidprv:attr/materialColorSurfaceBright"
-        android:endColor="?androidprv:attr/materialColorSurfaceBright" />
+        android:centerColor="?attr/materialColorSurfaceBright"
+        android:endColor="?attr/materialColorSurfaceBright" />
     <corners android:radius="@dimen/dialogCornerRadius" />
 </shape>
\ No newline at end of file
diff --git a/res/drawable/private_space_install_app_icon.xml b/res/drawable/private_space_install_app_icon.xml
index cfec2b1..12c4a82 100644
--- a/res/drawable/private_space_install_app_icon.xml
+++ b/res/drawable/private_space_install_app_icon.xml
@@ -23,9 +23,9 @@
             android:pathData="M30 0H30A30 30 0 0 1 60 30V30A30 30 0 0 1 30 60H30A30 30 0 0 1 0 30V30A30 30 0 0 1 30 0Z" />
         <path
             android:pathData="M30 0H30A30 30 0 0 1 60 30V30A30 30 0 0 1 30 60H30A30 30 0 0 1 0 30V30A30 30 0 0 1 30 0Z"
-            android:fillColor="@color/material_color_surface_container_lowest" />
+            android:fillColor="?attr/materialColorSurfaceContainerLowest" />
         <path
             android:pathData="M29 31h-6v-2h6v-6h2v6h6v2h-6v6h-2v-6Z"
-            android:fillColor="@color/material_color_on_surface" />
+            android:fillColor="?attr/materialColorOnSurface" />
     </group>
 </vector>
diff --git a/res/drawable/rounded_action_button.xml b/res/drawable/rounded_action_button.xml
index e283d3f..ddd3042 100644
--- a/res/drawable/rounded_action_button.xml
+++ b/res/drawable/rounded_action_button.xml
@@ -7,7 +7,7 @@
   ~
   ~      http://www.apache.org/licenses/LICENSE-2.0
   ~
-  ~ Unless required by applicable law or agreed to in writing, software
+  ~ Unless required by applicable law or agreed to in writing, soft]ware
   ~ 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
@@ -16,13 +16,12 @@
 
 
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     android:shape="rectangle">
-    <solid android:color="@color/material_color_surface_container_low" />
+    <solid android:color="?attr/materialColorSurfaceContainerLow" />
     <corners android:radius="@dimen/rounded_button_radius" />
     <stroke
         android:width="1dp"
-        android:color="@color/material_color_surface_container_low" />
+        android:color="?attr/materialColorSurfaceContainerLow" />
     <padding
         android:left="@dimen/rounded_button_padding"
         android:right="@dimen/rounded_button_padding" />
diff --git a/res/drawable/work_card.xml b/res/drawable/work_card.xml
index 9bf2b8d..01ec947 100644
--- a/res/drawable/work_card.xml
+++ b/res/drawable/work_card.xml
@@ -17,7 +17,7 @@
 
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
     android:shape="rectangle">
-    <solid android:color="@color/material_color_surface_container_highest" />
+    <solid android:color="?attr/materialColorSurfaceContainerHighest" />
     <corners android:radius="@dimen/work_edu_card_radius" />
 </shape>
 
diff --git a/res/layout/private_space_header.xml b/res/layout/private_space_header.xml
index 52180cf..e68f0f3 100644
--- a/res/layout/private_space_header.xml
+++ b/res/layout/private_space_header.xml
@@ -61,7 +61,7 @@
                 android:layout_marginBottom="@dimen/ps_lock_icon_margin_bottom"
                 android:importantForAccessibility="no"
                 android:src="@drawable/ic_lock"
-                app:tint="@color/material_color_primary_fixed_dim"
+                app:tint="?attr/materialColorPrimaryFixedDim"
                 android:scaleType="center"/>
             <TextView
                 android:id="@+id/lock_text"
@@ -69,7 +69,7 @@
                 android:layout_height="wrap_content"
                 android:layout_marginStart="@dimen/ps_lock_icon_text_margin_start_expanded"
                 android:layout_marginEnd="@dimen/ps_lock_icon_text_margin_end_expanded"
-                android:textColor="@color/material_color_on_primary_fixed"
+                android:textColor="?attr/materialColorOnPrimaryFixed"
                 android:textSize="14sp"
                 android:text="@string/ps_container_lock_title"
                 android:maxLines="1"
diff --git a/res/layout/widgets_two_pane_sheet_paged_view.xml b/res/layout/widgets_two_pane_sheet_paged_view.xml
index 887efb8..1f41680 100644
--- a/res/layout/widgets_two_pane_sheet_paged_view.xml
+++ b/res/layout/widgets_two_pane_sheet_paged_view.xml
@@ -55,18 +55,39 @@
             android:clipToOutline="true"
             android:orientation="vertical">
 
-            <FrameLayout
+            <LinearLayout
                 android:id="@+id/search_bar_container"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
+                android:orientation="horizontal"
                 android:background="?attr/widgetPickerPrimarySurfaceColor"
-                android:clipToPadding="false"
-                android:elevation="0.1dp"
-                android:paddingBottom="8dp"
+                android:gravity="center_vertical"
                 launcher:layout_sticky="true">
+                <FrameLayout
+                    android:layout_width="0dp"
+                    android:layout_height="wrap_content"
+                    android:layout_weight="1"
+                    android:clipToPadding="false"
+                    android:elevation="0.1dp"
+                    android:paddingBottom="8dp">
 
-                <include layout="@layout/widgets_search_bar" />
-            </FrameLayout>
+                    <include layout="@layout/widgets_search_bar" />
+                </FrameLayout>
+
+                <ImageButton
+                    android:id="@+id/widget_picker_widget_options_menu"
+                    android:layout_width="48dp"
+                    android:layout_height="48dp"
+                    android:layout_marginStart="8dp"
+                    android:layout_marginBottom="8dp"
+                    android:layout_gravity="bottom"
+                    android:background="@drawable/full_rounded_transparent_ripple"
+                    android:contentDescription="@string/widget_picker_widget_options_button_description"
+                    android:padding="12dp"
+                    android:src="@drawable/ic_more_vert_dots"
+                    android:visibility="gone"
+                    android:tint="?attr/widgetPickerWidgetOptionsMenuColor" />
+            </LinearLayout>
 
             <FrameLayout
                 android:layout_width="match_parent"
diff --git a/res/layout/widgets_two_pane_sheet_recyclerview.xml b/res/layout/widgets_two_pane_sheet_recyclerview.xml
index f3d3b16..c6b3b74 100644
--- a/res/layout/widgets_two_pane_sheet_recyclerview.xml
+++ b/res/layout/widgets_two_pane_sheet_recyclerview.xml
@@ -39,19 +39,40 @@
             android:clipToOutline="true"
             android:orientation="vertical">
 
-            <FrameLayout
+            <LinearLayout
                 android:id="@+id/search_bar_container"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
+                android:gravity="center_vertical"
+                android:orientation="horizontal"
                 android:background="?attr/widgetPickerPrimarySurfaceColor"
-                android:clipToPadding="false"
-                android:elevation="0.1dp"
-                android:paddingBottom="16dp"
                 android:paddingHorizontal="@dimen/widget_list_horizontal_margin_two_pane"
                 launcher:layout_sticky="true">
+                <FrameLayout
+                    android:layout_width="0dp"
+                    android:layout_height="wrap_content"
+                    android:layout_weight="1"
+                    android:clipToPadding="false"
+                    android:elevation="0.1dp"
+                    android:paddingBottom="16dp">
 
-                <include layout="@layout/widgets_search_bar" />
-            </FrameLayout>
+                    <include layout="@layout/widgets_search_bar" />
+                </FrameLayout>
+
+                <ImageButton
+                    android:id="@+id/widget_picker_widget_options_menu"
+                    android:layout_width="48dp"
+                    android:layout_height="48dp"
+                    android:layout_marginStart="8dp"
+                    android:layout_marginBottom="16dp"
+                    android:layout_gravity="bottom"
+                    android:background="@drawable/full_rounded_transparent_ripple"
+                    android:contentDescription="@string/widget_picker_widget_options_button_description"
+                    android:padding="12dp"
+                    android:src="@drawable/ic_more_vert_dots"
+                    android:visibility="gone"
+                    android:tint="?attr/widgetPickerWidgetOptionsMenuColor" />
+            </LinearLayout>
 
             <FrameLayout
                 android:layout_width="match_parent"
diff --git a/res/values-night-v31/colors.xml b/res/values-night-v31/colors.xml
index 2688b83..07c450e 100644
--- a/res/values-night-v31/colors.xml
+++ b/res/values-night-v31/colors.xml
@@ -57,39 +57,4 @@
         @android:color/system_accent1_200</color>
     <color name="work_fab_icon_color">
         @android:color/system_accent1_900</color>
-
-    <color name="material_color_on_secondary_fixed_variant">@android:color/system_accent2_700</color>
-    <color name="material_color_on_tertiary_fixed_variant">@android:color/system_accent3_700</color>
-    <color name="material_color_on_primary_fixed_variant">@android:color/system_accent1_700</color>
-    <color name="material_color_on_secondary_container">@android:color/system_accent2_100</color>
-    <color name="material_color_on_tertiary_container">@android:color/system_accent3_100</color>
-    <color name="material_color_on_primary_container">@android:color/system_accent1_100</color>
-    <color name="material_color_secondary_fixed_dim">@android:color/system_accent2_200</color>
-    <color name="material_color_on_error_container">#FFDAD5</color>
-    <color name="material_color_on_secondary_fixed">@android:color/system_accent2_900</color>
-    <color name="material_color_on_surface_inverse">@android:color/system_neutral1_900</color>
-    <color name="material_color_tertiary_fixed_dim">@android:color/system_accent3_200</color>
-    <color name="material_color_on_tertiary_fixed">@android:color/system_accent3_900</color>
-    <color name="material_color_primary_fixed_dim">@android:color/system_accent1_200</color>
-    <color name="material_color_secondary_container">@android:color/system_accent2_700</color>
-    <color name="material_color_error_container">#930001</color>
-    <color name="material_color_on_primary_fixed">@android:color/system_accent1_900</color>
-    <color name="material_color_primary_inverse">@android:color/system_accent1_600</color>
-    <color name="material_color_secondary_fixed">@android:color/system_accent2_100</color>
-    <color name="material_color_tertiary_container">@android:color/system_accent3_700</color>
-    <color name="material_color_tertiary_fixed">@android:color/system_accent3_100</color>
-    <color name="material_color_primary_container">@android:color/system_accent1_700</color>
-    <color name="material_color_on_background">@android:color/system_neutral1_800</color>
-    <color name="material_color_primary_fixed">@android:color/system_accent1_100</color>
-    <color name="material_color_on_secondary">@android:color/system_accent2_800</color>
-    <color name="material_color_on_tertiary">@android:color/system_accent3_800</color>
-    <color name="material_color_on_error">#690001</color>
-    <color name="material_color_on_surface_variant">@android:color/system_neutral2_200</color>
-    <color name="material_color_outline">@android:color/system_neutral2_400</color>
-    <color name="material_color_outline_variant">@android:color/system_neutral2_700</color>
-    <color name="material_color_on_primary">@android:color/system_accent1_800</color>
-    <color name="material_color_on_surface">@android:color/system_neutral1_100</color>
-    <color name="material_color_primary">@android:color/system_accent1_200</color>
-    <color name="material_color_secondary">@android:color/system_accent2_200</color>
-    <color name="material_color_tertiary">@android:color/system_accent3_200</color>
 </resources>
\ No newline at end of file
diff --git a/res/values-night-v34/colors.xml b/res/values-night-v34/colors.xml
index af28119..abce763 100644
--- a/res/values-night-v34/colors.xml
+++ b/res/values-night-v34/colors.xml
@@ -27,4 +27,7 @@
         @android:color/system_on_surface_dark</color>
     <color name="widget_cell_subtitle_color_dark">
         @android:color/system_on_surface_variant_dark</color>
+    <color name="widget_picker_menu_options_color_dark">
+        @android:color/system_on_surface_variant_dark
+    </color>
 </resources>
diff --git a/res/values-night/colors.xml b/res/values-night/colors.xml
deleted file mode 100644
index 95b3a63..0000000
--- a/res/values-night/colors.xml
+++ /dev/null
@@ -1,64 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-**
-** Copyright 2023, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-<resources>
-    <color name="material_color_on_secondary_fixed_variant">#3F4759</color>
-    <color name="material_color_on_tertiary_fixed_variant">#583E5B</color>
-    <color name="material_color_surface_container_lowest">#0D0E11</color>
-    <color name="material_color_on_primary_fixed_variant">#2B4678</color>
-    <color name="material_color_on_secondary_container">#DBE2F9</color>
-    <color name="material_color_on_tertiary_container">#FBD7FC</color>
-    <color name="material_color_surface_container_low">#1B1B1F</color>
-    <color name="material_color_on_primary_container">#D8E2FF</color>
-    <color name="material_color_secondary_fixed_dim">#BFC6DC</color>
-    <color name="material_color_on_error_container">#FFDAD5</color>
-    <color name="material_color_on_secondary_fixed">#141B2C</color>
-    <color name="material_color_on_surface_inverse">#1B1B1F</color>
-    <color name="material_color_tertiary_fixed_dim">#DEBCDF</color>
-    <color name="material_color_on_tertiary_fixed">#29132D</color>
-    <color name="material_color_primary_fixed_dim">#ADC6FF</color>
-    <color name="material_color_secondary_container">#3F4759</color>
-    <color name="material_color_error_container">#930001</color>
-    <color name="material_color_on_primary_fixed">#001A41</color>
-    <color name="material_color_primary_inverse">#445E91</color>
-    <color name="material_color_secondary_fixed">#DBE2F9</color>
-    <color name="material_color_surface_inverse">#FAF9FD</color>
-    <color name="material_color_surface_variant">#44474F</color>
-    <color name="material_color_tertiary_container">#583E5B</color>
-    <color name="material_color_tertiary_fixed">#FBD7FC</color>
-    <color name="material_color_primary_container">#2B4678</color>
-    <color name="material_color_on_background">#E3E2E6</color>
-    <color name="material_color_primary_fixed">#D8E2FF</color>
-    <color name="material_color_on_secondary">#293041</color>
-    <color name="material_color_on_tertiary">#402843</color>
-    <color name="material_color_surface_dim">#121316</color>
-    <color name="material_color_surface_bright">#38393C</color>
-    <color name="material_color_on_error">#690001</color>
-    <color name="material_color_surface">#121316</color>
-    <color name="material_color_surface_container_high">#292A2D</color>
-    <color name="material_color_surface_container_highest">#343538</color>
-    <color name="material_color_on_surface_variant">#C4C6D0</color>
-    <color name="material_color_outline">#72747D</color>
-    <color name="material_color_outline_variant">#444746</color>
-    <color name="material_color_on_primary">#102F60</color>
-    <color name="material_color_on_surface">#E3E2E6</color>
-    <color name="material_color_surface_container">#1F1F23</color>
-    <color name="material_color_primary">#ADC6FF</color>
-    <color name="material_color_secondary">#BFC6DC</color>
-    <color name="material_color_tertiary">#DEBCDF</color>
-</resources>
diff --git a/res/values-night/styles.xml b/res/values-night/styles.xml
index c95722f..06f0eee 100644
--- a/res/values-night/styles.xml
+++ b/res/values-night/styles.xml
@@ -26,9 +26,64 @@
         <item name="android:backgroundDimEnabled">true</item>
     </style>
 
-    <style name="WidgetPickerActivityTheme" parent="@android:style/Theme.Translucent.NoTitleBar">
+    <style name="DynamicColorsBaseLauncherTheme" parent="@style/BaseLauncherTheme">
+        <item name="materialColorOnSecondaryFixedVariant">@color/system_on_secondary_fixed_variant</item>
+        <item name="materialColorOnTertiaryFixedVariant">@color/system_on_tertiary_fixed_variant</item>
+        <item name="materialColorSurfaceContainerLowest">@color/system_surface_container_lowest_dark</item>
+        <item name="materialColorOnPrimaryFixedVariant">@color/system_on_primary_fixed_variant</item>
+        <item name="materialColorOnSecondaryContainer">@color/system_on_secondary_container_dark</item>
+        <item name="materialColorOnTertiaryContainer">@color/system_on_tertiary_container_dark</item>
+        <item name="materialColorSurfaceContainerLow">@color/system_surface_container_low_dark</item>
+        <item name="materialColorOnPrimaryContainer">@color/system_on_primary_container_dark</item>
+        <item name="materialColorSecondaryFixedDim">@color/system_secondary_fixed_dim</item>
+        <item name="materialColorOnErrorContainer">@color/system_on_error_container_dark</item>
+        <item name="materialColorOnSecondaryFixed">@color/system_on_secondary_fixed</item>
+        <item name="materialColorOnSurfaceInverse">@color/system_on_surface_light</item>
+        <item name="materialColorTertiaryFixedDim">@color/system_tertiary_fixed_dim</item>
+        <item name="materialColorOnTertiaryFixed">@color/system_on_tertiary_fixed</item>
+        <item name="materialColorPrimaryFixedDim">@color/system_primary_fixed_dim</item>
+        <item name="materialColorSecondaryContainer">@color/system_secondary_container_dark</item>
+        <item name="materialColorErrorContainer">@color/system_error_container_dark</item>
+        <item name="materialColorOnPrimaryFixed">@color/system_on_primary_fixed</item>
+        <item name="materialColorPrimaryInverse">@color/system_primary_light</item>
+        <item name="materialColorSecondaryFixed">@color/system_secondary_fixed</item>
+        <item name="materialColorSurfaceInverse">@color/system_surface_light</item>
+        <item name="materialColorSurfaceVariant">@color/system_surface_variant_dark</item>
+        <item name="materialColorTertiaryContainer">@color/system_tertiary_container_dark</item>
+        <item name="materialColorTertiaryFixed">@color/system_tertiary_fixed</item>
+        <item name="materialColorPrimaryContainer">@color/system_primary_container_dark</item>
+        <item name="materialColorOnBackground">@color/system_on_background_dark</item>
+        <item name="materialColorPrimaryFixed">@color/system_primary_fixed</item>
+        <item name="materialColorOnSecondary">@color/system_on_secondary_dark</item>
+        <item name="materialColorOnTertiary">@color/system_on_tertiary_dark</item>
+        <item name="materialColorSurfaceDim">@color/system_surface_dim_dark</item>
+        <item name="materialColorSurfaceBright">@color/system_surface_bright_dark</item>
+        <item name="materialColorOnError">@color/system_on_error_dark</item>
+        <item name="materialColorSurface">@color/system_surface_dark</item>
+        <item name="materialColorSurfaceContainerHigh">@color/system_surface_container_high_dark</item>
+        <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_dark</item>
+        <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_dark</item>
+        <item name="materialColorOutline">@color/system_outline_dark</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_dark</item>
+        <item name="materialColorOnPrimary">@color/system_on_primary_dark</item>
+        <item name="materialColorOnSurface">@color/system_on_surface_dark</item>
+        <item name="materialColorSurfaceContainer">@color/system_surface_container_dark</item>
+        <item name="materialColorPrimary">@color/system_primary_dark</item>
+        <item name="materialColorSecondary">@color/system_secondary_dark</item>
+        <item name="materialColorTertiary">@color/system_tertiary_dark</item>
+        <item name="materialColorError">@color/system_error_dark</item>
+    </style>
+
+    <style name="WidgetPickerActivityTheme" parent="@android:style/Theme.DeviceDefault.DayNight">
         <item name="widgetsTheme">@style/WidgetContainerTheme.Dark</item>
         <item name="android:windowBackground">@android:color/transparent</item>
+        <item name="android:windowNoTitle">true</item>
+        <item name="android:windowContentOverlay">@null</item>
+        <item name="android:colorBackgroundCacheHint">@null</item>
+        <item name="android:windowIsTranslucent">true</item>
+        <item name="android:windowAnimationStyle">@android:style/Animation</item>
+
+        <item name="widgetsTheme">@style/WidgetContainerTheme.Dark</item>
         <item name="pageIndicatorDotColor">@color/page_indicator_dot_color_dark</item>
     </style>
 </resources>
diff --git a/res/values-v30/styles.xml b/res/values-v30/styles.xml
index ec5c113..a5c57c8 100644
--- a/res/values-v30/styles.xml
+++ b/res/values-v30/styles.xml
@@ -29,5 +29,6 @@
         <item name="android:windowLayoutInDisplayCutoutMode">always</item>
         <item name="android:enforceStatusBarContrast">false</item>
         <item name="android:enforceNavigationBarContrast">false</item>
+        <item name="materialColorOnPrimaryFixed">#FFFFFFFF</item>
     </style>
 </resources>
diff --git a/res/values-v31/colors.xml b/res/values-v31/colors.xml
index 7270366..5c81d49 100644
--- a/res/values-v31/colors.xml
+++ b/res/values-v31/colors.xml
@@ -110,39 +110,4 @@
         @android:color/system_accent1_900</color>
 
     <color name="overview_foreground_scrim_color">@android:color/system_neutral1_1000</color>
-
-    <color name="material_color_on_secondary_fixed_variant">@android:color/system_accent2_700</color>
-    <color name="material_color_on_tertiary_fixed_variant">@android:color/system_accent3_700</color>
-    <color name="material_color_on_primary_fixed_variant">@android:color/system_accent1_700</color>
-    <color name="material_color_on_secondary_container">@android:color/system_accent2_900</color>
-    <color name="material_color_on_tertiary_container">@android:color/system_accent3_900</color>
-    <color name="material_color_on_primary_container">@android:color/system_accent1_900</color>
-    <color name="material_color_secondary_fixed_dim">@android:color/system_accent2_200</color>
-    <color name="material_color_on_error_container">#410000</color>
-    <color name="material_color_on_secondary_fixed">@android:color/system_accent2_900</color>
-    <color name="material_color_on_surface_inverse">@android:color/system_neutral1_100</color>
-    <color name="material_color_tertiary_fixed_dim">@android:color/system_accent3_200</color>
-    <color name="material_color_on_tertiary_fixed">@android:color/system_accent3_900</color>
-    <color name="material_color_primary_fixed_dim">@android:color/system_accent1_200</color>
-    <color name="material_color_secondary_container">@android:color/system_accent2_100</color>
-    <color name="material_color_error_container">#FFDAD5</color>
-    <color name="material_color_on_primary_fixed">@android:color/system_accent1_900</color>
-    <color name="material_color_primary_inverse">@android:color/system_accent1_200</color>
-    <color name="material_color_secondary_fixed">@android:color/system_accent2_100</color>
-    <color name="material_color_tertiary_container">@android:color/system_accent3_100</color>
-    <color name="material_color_tertiary_fixed">@android:color/system_accent3_100</color>
-    <color name="material_color_primary_container">@android:color/system_accent1_100</color>
-    <color name="material_color_on_background">@android:color/system_neutral1_50</color>
-    <color name="material_color_primary_fixed">@android:color/system_accent1_100</color>
-    <color name="material_color_on_secondary">@android:color/system_accent2_0</color>
-    <color name="material_color_on_tertiary">@android:color/system_accent3_0</color>
-    <color name="material_color_on_error">#FFFFFF</color>
-    <color name="material_color_on_surface_variant">@android:color/system_neutral2_700</color>
-    <color name="material_color_outline">@android:color/system_neutral2_500</color>
-    <color name="material_color_outline_variant">@android:color/system_neutral2_200</color>
-    <color name="material_color_on_primary">@android:color/system_accent1_0</color>
-    <color name="material_color_on_surface">@android:color/system_neutral1_900</color>
-    <color name="material_color_primary">@android:color/system_accent1_600</color>
-    <color name="material_color_secondary">@android:color/system_accent2_600</color>
-    <color name="material_color_tertiary">@android:color/system_accent3_600</color>
 </resources>
diff --git a/res/values-v34/colors.xml b/res/values-v34/colors.xml
index 26d3712..4f3a769 100644
--- a/res/values-v34/colors.xml
+++ b/res/values-v34/colors.xml
@@ -27,4 +27,108 @@
         @android:color/system_on_surface_light</color>
     <color name="widget_cell_subtitle_color_light">
         @android:color/system_on_surface_variant_light</color>
+    <color name="widget_picker_menu_options_color_light">
+        @android:color/system_on_surface_variant_light
+    </color>
+
+    <color name="system_primary_container_light">@android:color/system_primary_container_light</color>
+    <color name="system_on_primary_container_light">@android:color/system_on_primary_container_light</color>
+    <color name="system_primary_light">@android:color/system_primary_light</color>
+    <color name="system_on_primary_light">@android:color/system_on_primary_light</color>
+    <color name="system_secondary_container_light">@android:color/system_secondary_container_light</color>
+    <color name="system_on_secondary_container_light">@android:color/system_on_secondary_container_light</color>
+    <color name="system_secondary_light">@android:color/system_secondary_light</color>
+    <color name="system_on_secondary_light">@android:color/system_on_secondary_light</color>
+    <color name="system_tertiary_container_light">@android:color/system_tertiary_container_light</color>
+    <color name="system_on_tertiary_container_light">@android:color/system_on_tertiary_container_light</color>
+    <color name="system_tertiary_light">@android:color/system_tertiary_light</color>
+    <color name="system_on_tertiary_light">@android:color/system_on_tertiary_light</color>
+    <color name="system_background_light">@android:color/system_background_light</color>
+    <color name="system_on_background_light">@android:color/system_on_background_light</color>
+    <color name="system_surface_light">@android:color/system_surface_light</color>
+    <color name="system_on_surface_light">@android:color/system_on_surface_light</color>
+    <color name="system_surface_container_low_light">@android:color/system_surface_container_low_light</color>
+    <color name="system_surface_container_lowest_light">@android:color/system_surface_container_lowest_light</color>
+    <color name="system_surface_container_light">@android:color/system_surface_container_light</color>
+    <color name="system_surface_container_high_light">@android:color/system_surface_container_high_light</color>
+    <color name="system_surface_container_highest_light">@android:color/system_surface_container_highest_light</color>
+    <color name="system_surface_bright_light">@android:color/system_surface_bright_light</color>
+    <color name="system_surface_dim_light">@android:color/system_surface_dim_light</color>
+    <color name="system_surface_variant_light">@android:color/system_surface_variant_light</color>
+    <color name="system_on_surface_variant_light">@android:color/system_on_surface_variant_light</color>
+    <color name="system_outline_light">@android:color/system_outline_light</color>
+    <color name="system_outline_variant_light">@android:color/system_outline_variant_light</color>
+    <color name="system_error_light">@android:color/system_error_light</color>
+    <color name="system_on_error_light">@android:color/system_on_error_light</color>
+    <color name="system_error_container_light">@android:color/system_error_container_light</color>
+    <color name="system_on_error_container_light">@android:color/system_on_error_container_light</color>
+    <color name="system_control_activated_light">@android:color/system_control_activated_light</color>
+    <color name="system_control_normal_light">@android:color/system_control_normal_light</color>
+    <color name="system_control_highlight_light">@android:color/system_control_highlight_light</color>
+    <color name="system_text_primary_inverse_light">@android:color/system_text_primary_inverse_light</color>
+    <color name="system_text_secondary_and_tertiary_inverse_light">@android:color/system_text_secondary_and_tertiary_inverse_light</color>
+    <color name="system_text_primary_inverse_disable_only_light">@android:color/system_text_primary_inverse_disable_only_light</color>
+    <color name="system_text_secondary_and_tertiary_inverse_disabled_light">@android:color/system_text_secondary_and_tertiary_inverse_disabled_light</color>
+    <color name="system_text_hint_inverse_light">@android:color/system_text_hint_inverse_light</color>
+    <color name="system_palette_key_color_primary_light">@android:color/system_palette_key_color_primary_light</color>
+    <color name="system_palette_key_color_secondary_light">@android:color/system_palette_key_color_secondary_light</color>
+    <color name="system_palette_key_color_tertiary_light">@android:color/system_palette_key_color_tertiary_light</color>
+    <color name="system_palette_key_color_neutral_light">@android:color/system_palette_key_color_neutral_light</color>
+    <color name="system_palette_key_color_neutral_variant_light">@android:color/system_palette_key_color_neutral_variant_light</color>
+    <color name="system_primary_container_dark">@android:color/system_primary_container_dark</color>
+    <color name="system_on_primary_container_dark">@android:color/system_on_primary_container_dark</color>
+    <color name="system_primary_dark">@android:color/system_primary_dark</color>
+    <color name="system_on_primary_dark">@android:color/system_on_primary_dark</color>
+    <color name="system_secondary_container_dark">@android:color/system_secondary_container_dark</color>
+    <color name="system_on_secondary_container_dark">@android:color/system_on_secondary_container_dark</color>
+    <color name="system_secondary_dark">@android:color/system_secondary_dark</color>
+    <color name="system_on_secondary_dark">@android:color/system_on_secondary_dark</color>
+    <color name="system_tertiary_container_dark">@android:color/system_tertiary_container_dark</color>
+    <color name="system_on_tertiary_container_dark">@android:color/system_on_tertiary_container_dark</color>
+    <color name="system_tertiary_dark">@android:color/system_tertiary_dark</color>
+    <color name="system_on_tertiary_dark">@android:color/system_on_tertiary_dark</color>
+    <color name="system_background_dark">@android:color/system_background_dark</color>
+    <color name="system_on_background_dark">@android:color/system_on_background_dark</color>
+    <color name="system_surface_dark">@android:color/system_surface_dark</color>
+    <color name="system_on_surface_dark">@android:color/system_on_surface_dark</color>
+    <color name="system_surface_container_low_dark">@android:color/system_surface_container_low_dark</color>
+    <color name="system_surface_container_lowest_dark">@android:color/system_surface_container_lowest_dark</color>
+    <color name="system_surface_container_dark">@android:color/system_surface_container_dark</color>
+    <color name="system_surface_container_high_dark">@android:color/system_surface_container_high_dark</color>
+    <color name="system_surface_container_highest_dark">@android:color/system_surface_container_highest_dark</color>
+    <color name="system_surface_bright_dark">@android:color/system_surface_bright_dark</color>
+    <color name="system_surface_dim_dark">@android:color/system_surface_dim_dark</color>
+    <color name="system_surface_variant_dark">@android:color/system_surface_variant_dark</color>
+    <color name="system_on_surface_variant_dark">@android:color/system_on_surface_variant_dark</color>
+    <color name="system_outline_dark">@android:color/system_outline_dark</color>
+    <color name="system_outline_variant_dark">@android:color/system_outline_variant_dark</color>
+    <color name="system_error_dark">@android:color/system_error_dark</color>
+    <color name="system_on_error_dark">@android:color/system_on_error_dark</color>
+    <color name="system_error_container_dark">@android:color/system_error_container_dark</color>
+    <color name="system_on_error_container_dark">@android:color/system_on_error_container_dark</color>
+    <color name="system_control_activated_dark">@android:color/system_control_activated_dark</color>
+    <color name="system_control_normal_dark">@android:color/system_control_normal_dark</color>
+    <color name="system_control_highlight_dark">@android:color/system_control_highlight_dark</color>
+    <color name="system_text_primary_inverse_dark">@android:color/system_text_primary_inverse_dark</color>
+    <color name="system_text_secondary_and_tertiary_inverse_dark">@android:color/system_text_secondary_and_tertiary_inverse_dark</color>
+    <color name="system_text_primary_inverse_disable_only_dark">@android:color/system_text_primary_inverse_disable_only_dark</color>
+    <color name="system_text_secondary_and_tertiary_inverse_disabled_dark">@android:color/system_text_secondary_and_tertiary_inverse_disabled_dark</color>
+    <color name="system_text_hint_inverse_dark">@android:color/system_text_hint_inverse_dark</color>
+    <color name="system_palette_key_color_primary_dark">@android:color/system_palette_key_color_primary_dark</color>
+    <color name="system_palette_key_color_secondary_dark">@android:color/system_palette_key_color_secondary_dark</color>
+    <color name="system_palette_key_color_tertiary_dark">@android:color/system_palette_key_color_tertiary_dark</color>
+    <color name="system_palette_key_color_neutral_dark">@android:color/system_palette_key_color_neutral_dark</color>
+    <color name="system_palette_key_color_neutral_variant_dark">@android:color/system_palette_key_color_neutral_variant_dark</color>
+    <color name="system_primary_fixed">@android:color/system_primary_fixed</color>
+    <color name="system_primary_fixed_dim">@android:color/system_primary_fixed_dim</color>
+    <color name="system_on_primary_fixed">@android:color/system_on_primary_fixed</color>
+    <color name="system_on_primary_fixed_variant">@android:color/system_on_primary_fixed_variant</color>
+    <color name="system_secondary_fixed">@android:color/system_secondary_fixed</color>
+    <color name="system_secondary_fixed_dim">@android:color/system_secondary_fixed_dim</color>
+    <color name="system_on_secondary_fixed">@android:color/system_on_secondary_fixed</color>
+    <color name="system_on_secondary_fixed_variant">@android:color/system_on_secondary_fixed_variant</color>
+    <color name="system_tertiary_fixed">@android:color/system_tertiary_fixed</color>
+    <color name="system_tertiary_fixed_dim">@android:color/system_tertiary_fixed_dim</color>
+    <color name="system_on_tertiary_fixed">@android:color/system_on_tertiary_fixed</color>
+    <color name="system_on_tertiary_fixed_variant">@android:color/system_on_tertiary_fixed_variant</color>
 </resources>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index e4e047e..6151b5f 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -45,6 +45,55 @@
     <attr name="focusOutlineColor" format="color" />
     <attr name="focusInnerOutlineColor" format="color" />
 
+    <!--    Recreating Dynamic Color attributes found in the system. This should be the way to go on
+     all launcher projects since they all inherit from launcher3. Avoid creating other color
+     attributes if these can be user directly. -->
+    <attr name="materialColorOnSecondaryFixedVariant" format="color" />
+    <attr name="materialColorOnTertiaryFixedVariant" format="color" />
+    <attr name="materialColorSurfaceContainerLowest" format="color" />
+    <attr name="materialColorOnPrimaryFixedVariant" format="color" />
+    <attr name="materialColorOnSecondaryContainer" format="color" />
+    <attr name="materialColorOnTertiaryContainer" format="color" />
+    <attr name="materialColorSurfaceContainerLow" format="color" />
+    <attr name="materialColorOnPrimaryContainer" format="color" />
+    <attr name="materialColorSecondaryFixedDim" format="color" />
+    <attr name="materialColorOnErrorContainer" format="color" />
+    <attr name="materialColorOnSecondaryFixed" format="color" />
+    <attr name="materialColorOnSurfaceInverse" format="color" />
+    <attr name="materialColorTertiaryFixedDim" format="color" />
+    <attr name="materialColorOnTertiaryFixed" format="color" />
+    <attr name="materialColorPrimaryFixedDim" format="color" />
+    <attr name="materialColorSecondaryContainer" format="color" />
+    <attr name="materialColorErrorContainer" format="color" />
+    <attr name="materialColorOnPrimaryFixed" format="color" />
+    <attr name="materialColorPrimaryInverse" format="color" />
+    <attr name="materialColorSecondaryFixed" format="color" />
+    <attr name="materialColorSurfaceInverse" format="color" />
+    <attr name="materialColorSurfaceVariant" format="color" />
+    <attr name="materialColorTertiaryContainer" format="color" />
+    <attr name="materialColorTertiaryFixed" format="color" />
+    <attr name="materialColorPrimaryContainer" format="color" />
+    <attr name="materialColorOnBackground" format="color" />
+    <attr name="materialColorPrimaryFixed" format="color" />
+    <attr name="materialColorOnSecondary" format="color" />
+    <attr name="materialColorOnTertiary" format="color" />
+    <attr name="materialColorSurfaceDim" format="color" />
+    <attr name="materialColorSurfaceBright" format="color" />
+    <attr name="materialColorOnError" format="color" />
+    <attr name="materialColorSurface" format="color" />
+    <attr name="materialColorSurfaceContainerHigh" format="color" />
+    <attr name="materialColorSurfaceContainerHighest" format="color" />
+    <attr name="materialColorOnSurfaceVariant" format="color" />
+    <attr name="materialColorOutline" format="color" />
+    <attr name="materialColorOutlineVariant" format="color" />
+    <attr name="materialColorOnPrimary" format="color" />
+    <attr name="materialColorOnSurface" format="color" />
+    <attr name="materialColorSurfaceContainer" format="color" />
+    <attr name="materialColorPrimary" format="color" />
+    <attr name="materialColorSecondary" format="color" />
+    <attr name="materialColorTertiary" format="color" />
+    <attr name="materialColorError" format="color" />
+
     <attr name="pageIndicatorDotColor" format="color" />
     <attr name="folderPreviewColor" format="color" />
     <attr name="folderBackgroundColor" format="color" />
@@ -62,6 +111,7 @@
     <attr name="preloadIconBackgroundColor" format="color" />
     <attr name="widgetPickerTitleColor" format="color"/>
     <attr name="widgetPickerDescriptionColor" format="color"/>
+    <attr name="widgetPickerWidgetOptionsMenuColor" format="color"/>
     <attr name="widgetPickerPrimarySurfaceColor" format="color"/>
     <attr name="widgetPickerSecondarySurfaceColor" format="color"/>
     <attr name="widgetPickerHeaderAppTitleColor" format="color"/>
@@ -581,51 +631,6 @@
         <attr name="collapsable" format="boolean" />
     </declare-styleable>
 
-    <attr name="materialColorOnSecondaryFixedVariant" format="color" />
-    <attr name="materialColorOnTertiaryFixedVariant" format="color" />
-    <attr name="materialColorSurfaceContainerLowest" format="color" />
-    <attr name="materialColorOnPrimaryFixedVariant" format="color" />
-    <attr name="materialColorOnSecondaryContainer" format="color" />
-    <attr name="materialColorOnTertiaryContainer" format="color" />
-    <attr name="materialColorSurfaceContainerLow" format="color" />
-    <attr name="materialColorOnPrimaryContainer" format="color" />
-    <attr name="materialColorSecondaryFixedDim" format="color" />
-    <attr name="materialColorOnErrorContainer" format="color" />
-    <attr name="materialColorOnSecondaryFixed" format="color" />
-    <attr name="materialColorOnSurfaceInverse" format="color" />
-    <attr name="materialColorTertiaryFixedDim" format="color" />
-    <attr name="materialColorOnTertiaryFixed" format="color" />
-    <attr name="materialColorPrimaryFixedDim" format="color" />
-    <attr name="materialColorSecondaryContainer" format="color" />
-    <attr name="materialColorErrorContainer" format="color" />
-    <attr name="materialColorOnPrimaryFixed" format="color" />
-    <attr name="materialColorPrimaryInverse" format="color" />
-    <attr name="materialColorSecondaryFixed" format="color" />
-    <attr name="materialColorTertiaryContainer" format="color" />
-    <attr name="materialColorTertiaryFixed" format="color" />
-    <attr name="materialColorPrimaryContainer" format="color" />
-    <attr name="materialColorOnBackground" format="color" />
-    <attr name="materialColorPrimaryFixed" format="color" />
-    <attr name="materialColorOnSecondary" format="color" />
-    <attr name="materialColorOnTertiary" format="color" />
-    <attr name="materialColorOnError" format="color" />
-    <attr name="materialColorOnSurfaceVariant" format="color" />
-    <attr name="materialColorOutline" format="color" />
-    <attr name="materialColorOutlineVariant" format="color" />
-    <attr name="materialColorOnPrimary" format="color" />
-    <attr name="materialColorOnSurface" format="color" />
-    <attr name="materialColorPrimary" format="color" />
-    <attr name="materialColorSecondary" format="color" />
-    <attr name="materialColorTertiary" format="color" />
-    <attr name="materialColorSurfaceInverse" format="color" />
-    <attr name="materialColorSurfaceVariant" format="color" />
-    <attr name="materialColorSurfaceDim" format="color" />
-    <attr name="materialColorSurfaceBright" format="color" />
-    <attr name="materialColorSurface" format="color" />
-    <attr name="materialColorSurfaceContainerHigh" format="color" />
-    <attr name="materialColorSurfaceContainerHighest" format="color" />
-    <attr name="materialColorSurfaceContainer" format="color" />
-
     <declare-styleable name="WidgetSections">
         <!-- Component name of an app widget provider. -->
         <attr name="provider" format="string" />
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 8fa1992..c82358b 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -17,8 +17,7 @@
 ** limitations under the License.
 */
 -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
     <!-- The color tints to apply to the text and drag view when hovering
          over the delete target or the info target -->
     <color name="delete_target_hover_tint">#FFC1C1C1</color>
@@ -105,6 +104,7 @@
     <color name="widget_picker_secondary_surface_color_light">#FAF9F8</color>
     <color name="widget_picker_title_color_light">#1F1F1F</color>
     <color name="widget_picker_description_color_light">#4C4D50</color>
+    <color name="widget_picker_menu_options_color_light">@color/system_on_surface_variant_light</color>
     <color name="widget_picker_header_app_title_color_light">#1F1F1F</color>
     <color name="widget_picker_header_app_subtitle_color_light">#444746</color>
     <color name="widget_picker_header_background_color_light">#C2E7FF</color>
@@ -118,13 +118,14 @@
     <color name="widget_picker_collapse_handle_color_light">#C4C7C5</color>
     <color name="widget_picker_add_button_background_color_light">#0B57D0</color>
     <color name="widget_picker_add_button_text_color_light">#0B57D0</color>
-    <color name="widget_cell_title_color_light">@color/material_color_on_surface</color>
-    <color name="widget_cell_subtitle_color_light">@color/material_color_on_surface_variant</color>
+    <color name="widget_cell_title_color_light">@color/system_on_surface_light</color>
+    <color name="widget_cell_subtitle_color_light">@color/system_on_surface_variant_light</color>
 
     <color name="widget_picker_primary_surface_color_dark">#1F2020</color>
     <color name="widget_picker_secondary_surface_color_dark">#393939</color>
     <color name="widget_picker_title_color_dark">#E3E3E3</color>
     <color name="widget_picker_description_color_dark">#CCCDCF</color>
+    <color name="widget_picker_menu_options_color_dark">@color/system_on_surface_variant_dark</color>
     <color name="widget_picker_header_app_title_color_dark">#E3E3E3</color>
     <color name="widget_picker_header_app_subtitle_color_dark">#C4C7C5</color>
     <color name="widget_picker_header_background_color_dark">#004A77</color>
@@ -138,51 +139,107 @@
     <color name="widget_picker_collapse_handle_color_dark">#444746</color>
     <color name="widget_picker_add_button_background_color_dark">#062E6F</color>
     <color name="widget_picker_add_button_text_color_dark">#FFFFFF</color>
-    <color name="widget_cell_title_color_dark">@color/material_color_on_surface</color>
-    <color name="widget_cell_subtitle_color_dark">@color/material_color_on_surface_variant</color>
+    <color name="widget_cell_title_color_dark">@color/system_on_surface_dark</color>
+    <color name="widget_cell_subtitle_color_dark">@color/system_on_surface_variant_dark</color>
 
-    <color name="material_color_on_secondary_fixed_variant">#3F4759</color>
-    <color name="material_color_on_tertiary_fixed_variant">#583E5B</color>
-    <color name="material_color_surface_container_lowest">#FFFFFF</color>
-    <color name="material_color_on_primary_fixed_variant">#2B4678</color>
-    <color name="material_color_on_secondary_container">#141B2C</color>
-    <color name="material_color_on_tertiary_container">#29132D</color>
-    <color name="material_color_surface_container_low">#F5F3F7</color>
-    <color name="material_color_on_primary_container">#001A41</color>
-    <color name="material_color_secondary_fixed_dim">#BFC6DC</color>
-    <color name="material_color_on_error_container">#410000</color>
-    <color name="material_color_on_secondary_fixed">#141B2C</color>
-    <color name="material_color_on_surface_inverse">#E3E2E6</color>
-    <color name="material_color_tertiary_fixed_dim">#DEBCDF</color>
-    <color name="material_color_on_tertiary_fixed">#29132D</color>
-    <color name="material_color_primary_fixed_dim">#ADC6FF</color>
-    <color name="material_color_secondary_container">#DBE2F9</color>
-    <color name="material_color_error_container">#FFDAD5</color>
-    <color name="material_color_on_primary_fixed">#001A41</color>
-    <color name="material_color_primary_inverse">#ADC6FF</color>
-    <color name="material_color_secondary_fixed">#DBE2F9</color>
-    <color name="material_color_surface_inverse">#121316</color>
-    <color name="material_color_surface_variant">#E1E2EC</color>
-    <color name="material_color_tertiary_container">#FBD7FC</color>
-    <color name="material_color_tertiary_fixed">#FBD7FC</color>
-    <color name="material_color_primary_container">#D8E2FF</color>
-    <color name="material_color_on_background">#1B1B1F</color>
-    <color name="material_color_primary_fixed">#D8E2FF</color>
-    <color name="material_color_on_secondary">#FFFFFF</color>
-    <color name="material_color_on_tertiary">#FFFFFF</color>
-    <color name="material_color_surface_dim">#DBD9DD</color>
-    <color name="material_color_surface_bright">#FAF9FD</color>
-    <color name="material_color_on_error">#FFFFFF</color>
-    <color name="material_color_surface">#FAF9FD</color>
-    <color name="material_color_surface_container_high">#E9E7EC</color>
-    <color name="material_color_surface_container_highest">#E3E2E6</color>
-    <color name="material_color_on_surface_variant">#44474F</color>
-    <color name="material_color_outline">#72747D</color>
-    <color name="material_color_outline_variant">#C4C7C5</color>
-    <color name="material_color_on_primary">#FFFFFF</color>
-    <color name="material_color_on_surface">#1B1B1F</color>
-    <color name="material_color_surface_container">#EFEDF1</color>
-    <color name="material_color_primary">#445E91</color>
-    <color name="material_color_secondary">#575E71</color>
-    <color name="material_color_tertiary">#715573</color>
+    <color name="system_primary_container_light">#D9E2FF</color>
+    <color name="system_on_primary_container_light">#001945</color>
+    <color name="system_primary_light">#475D92</color>
+    <color name="system_on_primary_light">#FFFFFF</color>
+    <color name="system_secondary_container_light">#DCE2F9</color>
+    <color name="system_on_secondary_container_light">#151B2C</color>
+    <color name="system_secondary_light">#575E71</color>
+    <color name="system_on_secondary_light">#FFFFFF</color>
+    <color name="system_tertiary_container_light">#FDD7FA</color>
+    <color name="system_on_tertiary_container_light">#2A122C</color>
+    <color name="system_tertiary_light">#725572</color>
+    <color name="system_on_tertiary_light">#FFFFFF</color>
+    <color name="system_background_light">#FAF8FF</color>
+    <color name="system_on_background_light">#1A1B20</color>
+    <color name="system_surface_light">#FAF8FF</color>
+    <color name="system_on_surface_light">#1A1B20</color>
+    <color name="system_surface_container_low_light">#F4F3FA</color>
+    <color name="system_surface_container_lowest_light">#FFFFFF</color>
+    <color name="system_surface_container_light">#EEEDF4</color>
+    <color name="system_surface_container_high_light">#E8E7EF</color>
+    <color name="system_surface_container_highest_light">#E2E2E9</color>
+    <color name="system_surface_bright_light">#FAF8FF</color>
+    <color name="system_surface_dim_light">#DAD9E0</color>
+    <color name="system_surface_variant_light">#E1E2EC</color>
+    <color name="system_on_surface_variant_light">#44464F</color>
+    <color name="system_outline_light">#757780</color>
+    <color name="system_outline_variant_light">#C5C6D0</color>
+    <color name="system_error_light">#BA1A1A</color>
+    <color name="system_on_error_light">#FFFFFF</color>
+    <color name="system_error_container_light">#FFDAD6</color>
+    <color name="system_on_error_container_light">#410002</color>
+    <color name="system_control_activated_light">#D9E2FF</color>
+    <color name="system_control_normal_light">#44464F</color>
+    <color name="system_control_highlight_light">#000000</color>
+    <color name="system_text_primary_inverse_light">#E2E2E9</color>
+    <color name="system_text_secondary_and_tertiary_inverse_light">#C5C6D0</color>
+    <color name="system_text_primary_inverse_disable_only_light">#E2E2E9</color>
+    <color name="system_text_secondary_and_tertiary_inverse_disabled_light">#E2E2E9</color>
+    <color name="system_text_hint_inverse_light">#E2E2E9</color>
+    <color name="system_palette_key_color_primary_light">#6076AC</color>
+    <color name="system_palette_key_color_secondary_light">#70778B</color>
+    <color name="system_palette_key_color_tertiary_light">#8C6D8C</color>
+    <color name="system_palette_key_color_neutral_light">#76777D</color>
+    <color name="system_palette_key_color_neutral_variant_light">#757780</color>
+    <color name="system_primary_container_dark">#2F4578</color>
+    <color name="system_on_primary_container_dark">#D9E2FF</color>
+    <color name="system_primary_dark">#B0C6FF</color>
+    <color name="system_on_primary_dark">#152E60</color>
+    <color name="system_secondary_container_dark">#404659</color>
+    <color name="system_on_secondary_container_dark">#DCE2F9</color>
+    <color name="system_secondary_dark">#C0C6DC</color>
+    <color name="system_on_secondary_dark">#2A3042</color>
+    <color name="system_tertiary_container_dark">#593D59</color>
+    <color name="system_on_tertiary_container_dark">#FDD7FA</color>
+    <color name="system_tertiary_dark">#E0BBDD</color>
+    <color name="system_on_tertiary_dark">#412742</color>
+    <color name="system_background_dark">#121318</color>
+    <color name="system_on_background_dark">#E2E2E9</color>
+    <color name="system_surface_dark">#121318</color>
+    <color name="system_on_surface_dark">#E2E2E9</color>
+    <color name="system_surface_container_low_dark">#1A1B20</color>
+    <color name="system_surface_container_lowest_dark">#0C0E13</color>
+    <color name="system_surface_container_dark">#1E1F25</color>
+    <color name="system_surface_container_high_dark">#282A2F</color>
+    <color name="system_surface_container_highest_dark">#33343A</color>
+    <color name="system_surface_bright_dark">#38393F</color>
+    <color name="system_surface_dim_dark">#121318</color>
+    <color name="system_surface_variant_dark">#44464F</color>
+    <color name="system_on_surface_variant_dark">#C5C6D0</color>
+    <color name="system_outline_dark">#8F9099</color>
+    <color name="system_outline_variant_dark">#44464F</color>
+    <color name="system_error_dark">#FFB4AB</color>
+    <color name="system_on_error_dark">#690005</color>
+    <color name="system_error_container_dark">#93000A</color>
+    <color name="system_on_error_container_dark">#FFDAD6</color>
+    <color name="system_control_activated_dark">#2F4578</color>
+    <color name="system_control_normal_dark">#C5C6D0</color>
+    <color name="system_control_highlight_dark">#FFFFFF</color>
+    <color name="system_text_primary_inverse_dark">#1A1B20</color>
+    <color name="system_text_secondary_and_tertiary_inverse_dark">#44464F</color>
+    <color name="system_text_primary_inverse_disable_only_dark">#1A1B20</color>
+    <color name="system_text_secondary_and_tertiary_inverse_disabled_dark">#1A1B20</color>
+    <color name="system_text_hint_inverse_dark">#1A1B20</color>
+    <color name="system_palette_key_color_primary_dark">#6076AC</color>
+    <color name="system_palette_key_color_secondary_dark">#70778B</color>
+    <color name="system_palette_key_color_tertiary_dark">#8C6D8C</color>
+    <color name="system_palette_key_color_neutral_dark">#76777D</color>
+    <color name="system_palette_key_color_neutral_variant_dark">#757780</color>
+    <color name="system_primary_fixed">#D9E2FF</color>
+    <color name="system_primary_fixed_dim">#B0C6FF</color>
+    <color name="system_on_primary_fixed">#001945</color>
+    <color name="system_on_primary_fixed_variant">#2F4578</color>
+    <color name="system_secondary_fixed">#DCE2F9</color>
+    <color name="system_secondary_fixed_dim">#C0C6DC</color>
+    <color name="system_on_secondary_fixed">#151B2C</color>
+    <color name="system_on_secondary_fixed_variant">#404659</color>
+    <color name="system_tertiary_fixed">#FDD7FA</color>
+    <color name="system_tertiary_fixed_dim">#E0BBDD</color>
+    <color name="system_on_tertiary_fixed">#2A122C</color>
+    <color name="system_on_tertiary_fixed_variant">#593D59</color>
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index d33adc4..3b458c2 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -61,6 +61,12 @@
     <string name="long_press_widget_to_add">Touch &amp; hold to move a widget.</string>
     <!-- Accessibility spoken hint message in widget picker, which allows user to add a widget. Custom action is the label for additional accessibility actions available in this mode [CHAR_LIMIT=100] -->
     <string name="long_accessible_way_to_add">Double-tap &amp; hold to move a widget or use custom actions.</string>
+    <!-- Accessibility label for the icon button shown in the widget picker that opens a overflow
+         menu with widgets viewing options. [CHAR_LIMIT=25] -->
+    <string name="widget_picker_widget_options_button_description">More options</string>
+    <!-- Label for the checkbox shown in the widget picker toggles whether to show all widgets or
+     the default set. [CHAR_LIMIT=25] -->
+    <string name="widget_picker_show_all_widgets_menu_item_title">Show all widgets</string>
     <!-- The format string for the dimensions of a widget in the drawer -->
     <!-- There is a special version of this format string for Farsi -->
     <string name="widget_dims_format">%1$d \u00d7 %2$d</string>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index f7273a0..ee7ed26 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -17,7 +17,7 @@
 */
 -->
 
-<resources>
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
     <!-- Launcher theme -->
     <style name="BaseLauncherTheme" parent="@android:style/Theme.DeviceDefault.Light">
         <item name="disabledIconAlpha">.54</item>
@@ -29,17 +29,65 @@
         <item name="android:windowShowWallpaper">true</item>
     </style>
 
-    <style name="LauncherTheme" parent="@style/BaseLauncherTheme">
+    <style name="DynamicColorsBaseLauncherTheme" parent="@style/BaseLauncherTheme">
+        <item name="materialColorOnSecondaryFixedVariant">@color/system_on_secondary_fixed_variant</item>
+        <item name="materialColorOnTertiaryFixedVariant">@color/system_on_tertiary_fixed_variant</item>
+        <item name="materialColorSurfaceContainerLowest">@color/system_surface_container_lowest_light</item>
+        <item name="materialColorOnPrimaryFixedVariant">@color/system_on_primary_fixed_variant</item>
+        <item name="materialColorOnSecondaryContainer">@color/system_on_secondary_container_light</item>
+        <item name="materialColorOnTertiaryContainer">@color/system_on_tertiary_container_light</item>
+        <item name="materialColorSurfaceContainerLow">@color/system_surface_container_low_light</item>
+        <item name="materialColorOnPrimaryContainer">@color/system_on_primary_container_light</item>
+        <item name="materialColorSecondaryFixedDim">@color/system_secondary_fixed_dim</item>
+        <item name="materialColorOnErrorContainer">@color/system_on_error_container_light</item>
+        <item name="materialColorOnSecondaryFixed">@color/system_on_secondary_fixed</item>
+        <item name="materialColorOnSurfaceInverse">@color/system_on_surface_dark</item>
+        <item name="materialColorTertiaryFixedDim">@color/system_tertiary_fixed_dim</item>
+        <item name="materialColorOnTertiaryFixed">@color/system_on_tertiary_fixed</item>
+        <item name="materialColorPrimaryFixedDim">@color/system_primary_fixed_dim</item>
+        <item name="materialColorSecondaryContainer">@color/system_secondary_container_light</item>
+        <item name="materialColorErrorContainer">@color/system_error_container_light</item>
+        <item name="materialColorOnPrimaryFixed">@color/system_on_primary_fixed</item>
+        <item name="materialColorPrimaryInverse">@color/system_primary_dark</item>
+        <item name="materialColorSecondaryFixed">@color/system_secondary_fixed</item>
+        <item name="materialColorSurfaceInverse">@color/system_surface_dark</item>
+        <item name="materialColorSurfaceVariant">@color/system_surface_variant_light</item>
+        <item name="materialColorTertiaryContainer">@color/system_tertiary_container_light</item>
+        <item name="materialColorTertiaryFixed">@color/system_tertiary_fixed</item>
+        <item name="materialColorPrimaryContainer">@color/system_primary_container_light</item>
+        <item name="materialColorOnBackground">@color/system_on_background_light</item>
+        <item name="materialColorPrimaryFixed">@color/system_primary_fixed</item>
+        <item name="materialColorOnSecondary">@color/system_on_secondary_light</item>
+        <item name="materialColorOnTertiary">@color/system_on_tertiary_light</item>
+        <item name="materialColorSurfaceDim">@color/system_surface_dim_light</item>
+        <item name="materialColorSurfaceBright">@color/system_surface_bright_light</item>
+        <item name="materialColorOnError">@color/system_on_error_light</item>
+        <item name="materialColorSurface">@color/system_surface_light</item>
+        <item name="materialColorSurfaceContainerHigh">@color/system_surface_container_high_light</item>
+        <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_light</item>
+        <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_light</item>
+        <item name="materialColorOutline">@color/system_outline_light</item>
+        <item name="materialColorOutlineVariant">@color/system_outline_variant_light</item>
+        <item name="materialColorOnPrimary">@color/system_on_primary_light</item>
+        <item name="materialColorOnSurface">@color/system_on_surface_light</item>
+        <item name="materialColorSurfaceContainer">@color/system_surface_container_light</item>
+        <item name="materialColorPrimary">@color/system_primary_light</item>
+        <item name="materialColorSecondary">@color/system_secondary_light</item>
+        <item name="materialColorTertiary">@color/system_tertiary_light</item>
+        <item name="materialColorError">@color/system_error_light</item>
+    </style>
+
+
+    <style name="LauncherTheme" parent="@style/DynamicColorsBaseLauncherTheme">
         <item name="android:textColorSecondary">#DE000000</item>
         <item name="allAppsScrimColor">?attr/materialColorSurfaceDim</item>
-        <item name="allappsHeaderProtectionColor">
-            @color/material_color_surface_container_highest</item>
+        <item name="allappsHeaderProtectionColor">?attr/materialColorSurfaceContainerHighest</item>
         <item name="allAppsNavBarScrimColor">#66FFFFFF</item>
         <item name="popupColorPrimary">@color/popup_color_primary_light</item>
         <item name="popupColorSecondary">@color/popup_color_secondary_light</item>
         <item name="popupColorTertiary">@color/popup_color_tertiary_light</item>
         <item name="popupColorBackground">#EFEDED</item>
-        <item name="popupTextColor">@color/popup_text_color_light</item>
+        <item name="popupTextColor">@color/system_on_surface_light</item>
         <item name="popupShadeFirst">@color/popup_shade_first_light</item>
         <item name="popupShadeSecond">@color/popup_shade_second_light</item>
         <item name="popupShadeThird">@color/popup_shade_third_light</item>
@@ -53,15 +101,15 @@
         <item name="workspaceKeyShadowColor">#89000000</item>
         <item name="widgetsTheme">@style/WidgetContainerTheme</item>
         <item name="pageIndicatorDotColor">@color/page_indicator_dot_color_light</item>
-        <item name="focusOutlineColor">@color/material_color_secondary_fixed</item>
-        <item name="focusInnerOutlineColor">@color/material_color_on_secondary_fixed_variant</item>
+        <item name="focusOutlineColor">?attr/materialColorSecondaryFixed</item>
+        <item name="focusInnerOutlineColor">?attr/materialColorOnSecondaryFixedVariant</item>
         <item name="folderPreviewColor">@color/folder_preview_light</item>
         <item name="folderBackgroundColor">@color/folder_background_light</item>
         <item name="folderIconBorderColor">?android:attr/colorPrimary</item>
         <item name="isFolderDarkText">true</item>
         <item name="folderTextColor">@color/folder_text_color_light</item>
         <item name="folderHintTextColor">@color/folder_hint_text_color_light</item>
-        <item name="appPairSurfaceInFolder">@color/material_color_surface_container_lowest</item>
+        <item name="appPairSurfaceInFolder">?attr/materialColorSurfaceContainerLowest</item>
         <item name="loadingIconColor">#CCFFFFFF</item>
         <item name="iconOnlyShortcutColor">?android:attr/textColorSecondary</item>
         <item name="eduHalfSheetBGColor">?android:attr/colorAccent</item>
@@ -78,51 +126,6 @@
         <item name="android:statusBarColor">#00000000</item>
         <item name="android:navigationBarColor">#00000000</item>
         <item name="android:switchStyle">@style/SwitchStyle</item>
-
-        <item name="materialColorOnSecondaryFixedVariant">@color/material_color_on_secondary_fixed_variant</item>
-        <item name="materialColorOnTertiaryFixedVariant">@color/material_color_on_tertiary_fixed_variant</item>
-        <item name="materialColorSurfaceContainerLowest">@color/material_color_surface_container_lowest</item>
-        <item name="materialColorOnPrimaryFixedVariant">@color/material_color_on_primary_fixed_variant</item>
-        <item name="materialColorOnSecondaryContainer">@color/material_color_on_secondary_container</item>
-        <item name="materialColorOnTertiaryContainer">@color/material_color_on_tertiary_container</item>
-        <item name="materialColorSurfaceContainerLow">@color/material_color_surface_container_low</item>
-        <item name="materialColorOnPrimaryContainer">@color/material_color_on_primary_container</item>
-        <item name="materialColorSecondaryFixedDim">@color/material_color_secondary_fixed_dim</item>
-        <item name="materialColorOnErrorContainer">@color/material_color_on_error_container</item>
-        <item name="materialColorOnSecondaryFixed">@color/material_color_on_secondary_fixed</item>
-        <item name="materialColorOnSurfaceInverse">@color/material_color_on_surface_inverse</item>
-        <item name="materialColorTertiaryFixedDim">@color/material_color_tertiary_fixed_dim</item>
-        <item name="materialColorOnTertiaryFixed">@color/material_color_on_tertiary_fixed</item>
-        <item name="materialColorPrimaryFixedDim">@color/material_color_primary_fixed_dim</item>
-        <item name="materialColorSecondaryContainer">@color/material_color_secondary_container</item>
-        <item name="materialColorErrorContainer">@color/material_color_error_container</item>
-        <item name="materialColorOnPrimaryFixed">@color/material_color_on_primary_fixed</item>
-        <item name="materialColorPrimaryInverse">@color/material_color_primary_inverse</item>
-        <item name="materialColorSecondaryFixed">@color/material_color_secondary_fixed</item>
-        <item name="materialColorSurfaceInverse">@color/material_color_surface_inverse</item>
-        <item name="materialColorSurfaceVariant">@color/material_color_surface_variant</item>
-        <item name="materialColorTertiaryContainer">@color/material_color_tertiary_container</item>
-        <item name="materialColorTertiaryFixed">@color/material_color_tertiary_fixed</item>
-        <item name="materialColorPrimaryContainer">@color/material_color_primary_container</item>
-        <item name="materialColorOnBackground">@color/material_color_on_background</item>
-        <item name="materialColorPrimaryFixed">@color/material_color_primary_fixed</item>
-        <item name="materialColorOnSecondary">@color/material_color_on_secondary</item>
-        <item name="materialColorOnTertiary">@color/material_color_on_tertiary</item>
-        <item name="materialColorSurfaceDim">@color/material_color_surface_dim</item>
-        <item name="materialColorSurfaceBright">@color/material_color_surface_bright</item>
-        <item name="materialColorOnError">@color/material_color_on_error</item>
-        <item name="materialColorSurface">@color/material_color_surface</item>
-        <item name="materialColorSurfaceContainerHigh">@color/material_color_surface_container_high</item>
-        <item name="materialColorSurfaceContainerHighest">@color/material_color_surface_container_highest</item>
-        <item name="materialColorOnSurfaceVariant">@color/material_color_on_surface_variant</item>
-        <item name="materialColorOutline">@color/material_color_outline</item>
-        <item name="materialColorOutlineVariant">@color/material_color_outline_variant</item>
-        <item name="materialColorOnPrimary">@color/material_color_on_primary</item>
-        <item name="materialColorOnSurface">@color/material_color_on_surface</item>
-        <item name="materialColorSurfaceContainer">@color/material_color_surface_container</item>
-        <item name="materialColorPrimary">@color/material_color_primary</item>
-        <item name="materialColorSecondary">@color/material_color_secondary</item>
-        <item name="materialColorTertiary">@color/material_color_tertiary</item>
     </style>
 
     <style name="SwitchStyle"
@@ -153,13 +156,13 @@
         <item name="android:colorControlHighlight">#19FFFFFF</item>
         <item name="android:colorPrimary">#FF212121</item>
         <item name="allAppsScrimColor">?attr/materialColorSurfaceDim</item>
-        <item name="allappsHeaderProtectionColor">@color/material_color_surface_container_low</item>
+        <item name="allappsHeaderProtectionColor">?attr/materialColorSurfaceContainerLow</item>
         <item name="allAppsNavBarScrimColor">#80000000</item>
         <item name="popupColorPrimary">@color/popup_color_primary_dark</item>
         <item name="popupColorSecondary">@color/popup_color_secondary_dark</item>
         <item name="popupColorTertiary">@color/popup_color_tertiary_dark</item>
         <item name="popupColorBackground">#1F2020</item>
-        <item name="popupTextColor">@color/popup_text_color_dark</item>
+        <item name="popupTextColor">@color/system_on_surface_dark</item>
         <item name="popupNotificationDotColor">@color/popup_notification_dot_dark</item>
         <item name="popupShadeFirst">@color/popup_shade_first_dark</item>
         <item name="popupShadeSecond">@color/popup_shade_second_dark</item>
@@ -173,7 +176,7 @@
         <item name="isFolderDarkText">false</item>
         <item name="folderTextColor">@color/folder_text_color_dark</item>
         <item name="folderHintTextColor">@color/folder_hint_text_color_dark</item>
-        <item name="appPairSurfaceInFolder">@color/material_color_surface_container_lowest</item>
+        <item name="appPairSurfaceInFolder">?attr/materialColorSurfaceContainerLowest</item>
         <item name="isMainColorDark">true</item>
         <item name="loadingIconColor">#99FFFFFF</item>
         <item name="iconOnlyShortcutColor">#B3FFFFFF</item>
@@ -244,6 +247,7 @@
             @color/widget_picker_secondary_surface_color_light</item>
         <item name="widgetPickerTitleColor">@color/widget_picker_title_color_light</item>
         <item name="widgetPickerDescriptionColor">@color/widget_picker_description_color_light</item>
+        <item name="widgetPickerWidgetOptionsMenuColor">@color/widget_picker_menu_options_color_light</item>
         <item name="widgetPickerHeaderAppTitleColor">
             @color/widget_picker_header_app_title_color_light</item>
         <item name="widgetPickerHeaderAppSubtitleColor">
@@ -284,6 +288,7 @@
         <item name="widgetPickerTitleColor">
             @color/widget_picker_title_color_dark</item>
         <item name="widgetPickerDescriptionColor">@color/widget_picker_description_color_dark</item>
+        <item name="widgetPickerWidgetOptionsMenuColor">@color/widget_picker_menu_options_color_dark</item>
         <item name="widgetPickerHeaderAppTitleColor">
             @color/widget_picker_header_app_title_color_dark</item>
         <item name="widgetPickerHeaderAppSubtitleColor">
@@ -463,7 +468,7 @@
 
     <style name="PrivateSpaceHeaderTextStyle">
         <item name="android:textSize">16sp</item>
-        <item name="android:textColor">@color/material_color_on_surface</item>
+        <item name="android:textColor">?attr/materialColorOnSurface</item>
         <item name="android:fontFamily">google-sans-text-medium</item>
         <item name="android:ellipsize">end</item>
     </style>
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index cb897dc..5949732 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -70,6 +70,7 @@
 import static com.android.launcher3.Utilities.postAsyncCallback;
 import static com.android.launcher3.config.FeatureFlags.FOLDABLE_SINGLE_PAGE;
 import static com.android.launcher3.config.FeatureFlags.MULTI_SELECT_EDIT_MODE;
+import static com.android.launcher3.folder.FolderGridOrganizer.createFolderGridOrganizer;
 import static com.android.launcher3.logging.KeyboardStateManager.KeyboardState.HIDE;
 import static com.android.launcher3.logging.KeyboardStateManager.KeyboardState.SHOW;
 import static com.android.launcher3.logging.StatsLogManager.EventEnum;
@@ -188,7 +189,6 @@
 import com.android.launcher3.dragndrop.DragView;
 import com.android.launcher3.dragndrop.LauncherDragController;
 import com.android.launcher3.folder.Folder;
-import com.android.launcher3.folder.FolderGridOrganizer;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.keyboard.ViewGroupFocusHelper;
@@ -816,7 +816,7 @@
             View collectionIcon = getWorkspace().getHomescreenIconByItemId(info.container);
             if (collectionIcon instanceof FolderIcon folderIcon
                     && collectionIcon.getTag() instanceof FolderInfo) {
-                if (new FolderGridOrganizer(getDeviceProfile())
+                if (createFolderGridOrganizer(getDeviceProfile())
                         .setFolderInfo((FolderInfo) folderIcon.getTag())
                         .isItemInPreview(info.rank)) {
                     folderIcon.invalidate();
diff --git a/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.java b/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.java
deleted file mode 100644
index 79b8187..0000000
--- a/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (C) 2016 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.accessibility;
-
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewGroup.OnHierarchyChangeListener;
-
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.CellLayout;
-import com.android.launcher3.DropTarget.DragObject;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.dragndrop.DragController.DragListener;
-import com.android.launcher3.dragndrop.DragOptions;
-
-import java.util.function.Function;
-
-/**
- * Utility listener to enable/disable accessibility drag flags for a ViewGroup
- * containing CellLayouts
- */
-public class AccessibleDragListenerAdapter implements DragListener, OnHierarchyChangeListener {
-
-    private final ViewGroup mViewGroup;
-    private final Function<CellLayout, DragAndDropAccessibilityDelegate> mDelegateFactory;
-
-    /**
-     * @param parent the viewgroup containing all the children
-     * @param delegateFactory function to create no delegates
-     */
-    public AccessibleDragListenerAdapter(ViewGroup parent,
-            Function<CellLayout, DragAndDropAccessibilityDelegate> delegateFactory) {
-        mViewGroup = parent;
-        mDelegateFactory = delegateFactory;
-    }
-
-    @Override
-    public void onDragStart(DragObject dragObject, DragOptions options) {
-        mViewGroup.setOnHierarchyChangeListener(this);
-        enableAccessibleDrag(true, dragObject);
-    }
-
-    @Override
-    public void onDragEnd() {
-        mViewGroup.setOnHierarchyChangeListener(null);
-        enableAccessibleDrag(false, null);
-        Launcher.getLauncher(mViewGroup.getContext()).getDragController().removeDragListener(this);
-    }
-
-
-    @Override
-    public void onChildViewAdded(View parent, View child) {
-        if (parent == mViewGroup) {
-            setEnableForLayout((CellLayout) child, true);
-        }
-    }
-
-    @Override
-    public void onChildViewRemoved(View parent, View child) {
-        if (parent == mViewGroup) {
-            setEnableForLayout((CellLayout) child, false);
-        }
-    }
-
-    protected void enableAccessibleDrag(boolean enable, @Nullable DragObject dragObject) {
-        for (int i = 0; i < mViewGroup.getChildCount(); i++) {
-            setEnableForLayout((CellLayout) mViewGroup.getChildAt(i), enable);
-        }
-    }
-
-    protected final void setEnableForLayout(CellLayout layout, boolean enable) {
-        layout.setDragAndDropAccessibilityDelegate(enable ? mDelegateFactory.apply(layout) : null);
-    }
-}
diff --git a/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.kt b/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.kt
new file mode 100644
index 0000000..21c2caf
--- /dev/null
+++ b/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2016 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.accessibility
+
+import android.view.View
+import android.view.ViewGroup
+import com.android.launcher3.CellLayout
+import com.android.launcher3.DropTarget.DragObject
+import com.android.launcher3.dragndrop.DragController
+import com.android.launcher3.dragndrop.DragOptions
+import com.android.launcher3.views.ActivityContext
+import java.util.function.Function
+
+/**
+ * Utility listener to enable/disable accessibility drag flags for a ViewGroup containing
+ * CellLayouts
+ */
+open class AccessibleDragListenerAdapter
+/**
+ * @param parent the viewgroup containing all the children
+ * @param delegateFactory function to create no delegates
+ */
+(
+    private val mViewGroup: ViewGroup,
+    private val mDelegateFactory: Function<CellLayout, DragAndDropAccessibilityDelegate>
+) : DragController.DragListener, ViewGroup.OnHierarchyChangeListener {
+    override fun onDragStart(dragObject: DragObject, options: DragOptions) {
+        mViewGroup.setOnHierarchyChangeListener(this)
+        enableAccessibleDrag(true, dragObject)
+    }
+
+    override fun onDragEnd() {
+        mViewGroup.setOnHierarchyChangeListener(null)
+        enableAccessibleDrag(false, null)
+        val activityContext = ActivityContext.lookupContext(mViewGroup.context) as ActivityContext
+        activityContext.getDragController<DragController<*>>()?.removeDragListener(this)
+    }
+
+    override fun onChildViewAdded(parent: View, child: View) {
+        if (parent === mViewGroup) {
+            setEnableForLayout(child as CellLayout, true)
+        }
+    }
+
+    override fun onChildViewRemoved(parent: View, child: View) {
+        if (parent === mViewGroup) {
+            setEnableForLayout(child as CellLayout, false)
+        }
+    }
+
+    protected open fun enableAccessibleDrag(enable: Boolean, dragObject: DragObject?) {
+        for (i in 0 until mViewGroup.childCount) {
+            setEnableForLayout(mViewGroup.getChildAt(i) as CellLayout, enable)
+        }
+    }
+
+    protected fun setEnableForLayout(layout: CellLayout, enable: Boolean) {
+        layout.setDragAndDropAccessibilityDelegate(
+            if (enable) mDelegateFactory.apply(layout) else null
+        )
+    }
+}
diff --git a/src/com/android/launcher3/allapps/PrivateProfileManager.java b/src/com/android/launcher3/allapps/PrivateProfileManager.java
index aefb7e9..c1264d6 100644
--- a/src/com/android/launcher3/allapps/PrivateProfileManager.java
+++ b/src/com/android/launcher3/allapps/PrivateProfileManager.java
@@ -364,6 +364,7 @@
     /** Add Private Space Header view elements based upon {@link UserProfileState} */
     public void bindPrivateSpaceHeaderViewElements(RelativeLayout parent) {
         mPSHeader = parent;
+        Log.d(TAG, "bindPrivateSpaceHeaderViewElements: " + "Binding private space.");
         updateView();
         if (mOnPSHeaderAdded != null) {
             MAIN_EXECUTOR.execute(mOnPSHeaderAdded);
@@ -376,6 +377,8 @@
         if (mPSHeader == null) {
             return;
         }
+        Log.d(TAG, "bindPrivateSpaceHeaderViewElements: " + "Updating view with state: "
+                + getCurrentState());
         mPSHeader.setAlpha(1);
         ViewGroup lockPill = mPSHeader.findViewById(R.id.ps_lock_unlock_button);
         assert lockPill != null;
@@ -761,6 +764,9 @@
         alphaAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
             @Override
             public void onAnimationUpdate(ValueAnimator valueAnimator) {
+                if (mFloatingMaskView == null) {
+                    return;
+                }
                 mFloatingMaskView.setTranslationY((float) valueAnimator.getAnimatedValue());
             }
         });
@@ -844,8 +850,22 @@
         if (!Flags.privateSpaceAddFloatingMaskView()) {
             return;
         }
+        // Use getLocationOnScreen() as simply checking for mPSHeader.getBottom() is only relative
+        // to its parent.
+        int[] psHeaderLocation = new int[2];
+        mPSHeader.getLocationOnScreen(psHeaderLocation);
+        int psHeaderBottomY = psHeaderLocation[1] + mPsHeaderHeight;
+        // Calculate the topY of the floatingMaskView as if it was added.
+        int floatingMaskViewBottomBoxTopY =
+                (int) (mAllApps.getBottom() - getMainRecyclerView().getPaddingBottom());
+        // Don't attach if the header will be clipped by the floating mask view.
+        if (psHeaderBottomY > floatingMaskViewBottomBoxTopY) {
+            mFloatingMaskView = null;
+            return;
+        }
         mFloatingMaskView = (FloatingMaskView) mAllApps.getLayoutInflater().inflate(
                 R.layout.private_space_mask_view, mAllApps, false);
+        assert mFloatingMaskView != null;
         mAllApps.addView(mFloatingMaskView);
         // Translate off the screen first if its collapsing so this header view isn't visible to
         // user when animation starts.
diff --git a/src/com/android/launcher3/allapps/SectionDecorationHandler.java b/src/com/android/launcher3/allapps/SectionDecorationHandler.java
index ac9b146..eaeb8bb 100644
--- a/src/com/android/launcher3/allapps/SectionDecorationHandler.java
+++ b/src/com/android/launcher3/allapps/SectionDecorationHandler.java
@@ -25,7 +25,6 @@
 import android.view.View;
 
 import androidx.annotation.Nullable;
-import androidx.core.content.ContextCompat;
 
 import com.android.launcher3.R;
 import com.android.launcher3.util.Themes;
@@ -61,10 +60,10 @@
 
         mContext = context;
         mFillAlpha = fillAlpha;
-        mFocusColor = ContextCompat.getColor(context,
-                R.color.material_color_surface_bright); // UX recommended
-        mFillColor = ContextCompat.getColor(context,
-                R.color.material_color_surface_container_high); // UX recommended
+        mFocusColor = Themes.getAttrColor(context,
+                R.attr.materialColorSurfaceBright); // UX recommended
+        mFillColor = Themes.getAttrColor(context,
+                R.attr.materialColorSurfaceContainerHigh); // UX recommended
 
         mIsTopLeftRound = isTopLeftRound;
         mIsTopRightRound = isTopRightRound;
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index d3c1a02..175ab4e 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -26,6 +26,7 @@
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
 import static com.android.launcher3.config.FeatureFlags.ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS;
+import static com.android.launcher3.folder.FolderGridOrganizer.createFolderGridOrganizer;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_LABEL_UPDATED;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED;
 import static com.android.launcher3.testing.shared.TestProtocol.FOLDER_OPENED_MESSAGE;
@@ -1106,7 +1107,7 @@
     }
 
     private void updateItemLocationsInDatabaseBatch(boolean isBind) {
-        FolderGridOrganizer verifier = new FolderGridOrganizer(
+        FolderGridOrganizer verifier = createFolderGridOrganizer(
                 mActivityContext.getDeviceProfile()).setFolderInfo(mInfo);
 
         ArrayList<ItemInfo> items = new ArrayList<>();
@@ -1404,7 +1405,7 @@
 
     @Override
     public void onAdd(ItemInfo item, int rank) {
-        FolderGridOrganizer verifier = new FolderGridOrganizer(
+        FolderGridOrganizer verifier = createFolderGridOrganizer(
                 mActivityContext.getDeviceProfile()).setFolderInfo(mInfo);
         verifier.updateRankAndPos(item, rank);
         mLauncherDelegate.getModelWriter().addOrMoveItemInDatabase(item, mInfo.id, 0, item.cellX,
diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java
index 37a8d9b..3c4cf5a 100644
--- a/src/com/android/launcher3/folder/FolderAnimationManager.java
+++ b/src/com/android/launcher3/folder/FolderAnimationManager.java
@@ -21,6 +21,7 @@
 import static com.android.launcher3.BubbleTextView.TEXT_ALPHA_PROPERTY;
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
 import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
+import static com.android.launcher3.folder.FolderGridOrganizer.createFolderGridOrganizer;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -99,7 +100,7 @@
 
         mContext = folder.getContext();
         mDeviceProfile = folder.mActivityContext.getDeviceProfile();
-        mPreviewVerifier = new FolderGridOrganizer(mDeviceProfile);
+        mPreviewVerifier = createFolderGridOrganizer(mDeviceProfile);
 
         mIsOpening = isOpening;
 
diff --git a/src/com/android/launcher3/folder/FolderGridOrganizer.java b/src/com/android/launcher3/folder/FolderGridOrganizer.java
index 593673d..a7ab7b9 100644
--- a/src/com/android/launcher3/folder/FolderGridOrganizer.java
+++ b/src/com/android/launcher3/folder/FolderGridOrganizer.java
@@ -47,13 +47,20 @@
     /**
      * Note: must call {@link #setFolderInfo(FolderInfo)} manually for verifier to work.
      */
-    public FolderGridOrganizer(DeviceProfile profile) {
-        mMaxCountX = profile.numFolderColumns;
-        mMaxCountY = profile.numFolderRows;
+    public FolderGridOrganizer(int maxCountX, int maxCountY) {
+        mMaxCountX = maxCountX;
+        mMaxCountY = maxCountY;
         mMaxItemsPerPage = mMaxCountX * mMaxCountY;
     }
 
     /**
+     * Creates a FolderGridOrganizer for the given DeviceProfile
+     */
+    public static FolderGridOrganizer createFolderGridOrganizer(DeviceProfile profile) {
+        return new FolderGridOrganizer(profile.numFolderColumns, profile.numFolderRows);
+    }
+
+    /**
      * Updates the organizer with the provided folder info
      */
     public FolderGridOrganizer setFolderInfo(FolderInfo info) {
@@ -194,6 +201,7 @@
             int row = rank / mCountX;
             return col < PREVIEW_MAX_COLUMNS && row < PREVIEW_MAX_ROWS;
         }
+        // If we have less than 4 items do this
         return rank < MAX_NUM_ITEMS_IN_PREVIEW;
     }
 }
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 00636a3..e9859cf 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -19,6 +19,7 @@
 import static com.android.launcher3.Flags.enableCursorHoverStates;
 import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ICON_OVERLAP_FACTOR;
 import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
+import static com.android.launcher3.folder.FolderGridOrganizer.createFolderGridOrganizer;
 import static com.android.launcher3.folder.PreviewItemManager.INITIAL_ITEM_ANIMATION_DURATION;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_AUTO_LABELED;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_AUTO_LABELING_SKIPPED_EMPTY_PRIMARY;
@@ -223,7 +224,7 @@
 
         icon.setAccessibilityDelegate(activity.getAccessibilityDelegate());
 
-        icon.mPreviewVerifier = new FolderGridOrganizer(activity.getDeviceProfile());
+        icon.mPreviewVerifier = createFolderGridOrganizer(activity.getDeviceProfile());
         icon.mPreviewVerifier.setFolderInfo(folderInfo);
         icon.updatePreviewItems(false);
 
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index 8eaa0dc..1b5ef42 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -18,6 +18,7 @@
 
 import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
 import static com.android.launcher3.AbstractFloatingView.TYPE_FOLDER;
+import static com.android.launcher3.folder.FolderGridOrganizer.createFolderGridOrganizer;
 
 import android.annotation.SuppressLint;
 import android.content.Context;
@@ -100,10 +101,21 @@
     private boolean mViewsBound = false;
 
     public FolderPagedView(Context context, AttributeSet attrs) {
+        this(
+                context,
+                attrs,
+                createFolderGridOrganizer(ActivityContext.lookupContext(context).getDeviceProfile())
+        );
+    }
+
+    public FolderPagedView(
+            Context context,
+            AttributeSet attrs,
+            FolderGridOrganizer folderGridOrganizer
+    ) {
         super(context, attrs);
         ActivityContext activityContext = ActivityContext.lookupContext(context);
-        DeviceProfile profile = activityContext.getDeviceProfile();
-        mOrganizer = new FolderGridOrganizer(profile);
+        mOrganizer = folderGridOrganizer;
 
         mIsRtl = Utilities.isRtl(getResources());
         setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
diff --git a/src/com/android/launcher3/model/BaseLauncherBinder.java b/src/com/android/launcher3/model/BaseLauncherBinder.java
index e6ade61..5faa2b8 100644
--- a/src/com/android/launcher3/model/BaseLauncherBinder.java
+++ b/src/com/android/launcher3/model/BaseLauncherBinder.java
@@ -52,6 +52,7 @@
 import com.android.launcher3.util.LooperIdleLock;
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.RunnableList;
+import com.android.launcher3.widget.model.WidgetsListBaseEntriesBuilder;
 import com.android.launcher3.widget.model.WidgetsListBaseEntry;
 
 import java.util.ArrayList;
@@ -195,8 +196,8 @@
         if (!WIDGETS_ENABLED) {
             return;
         }
-        final List<WidgetsListBaseEntry> widgets =
-                mBgDataModel.widgetsModel.getWidgetsListForPicker(mApp.getContext());
+        List<WidgetsListBaseEntry> widgets = new WidgetsListBaseEntriesBuilder(mApp.getContext())
+                .build(mBgDataModel.widgetsModel.getWidgetsByPackageItem());
         executeCallbacksTask(c -> c.bindAllWidgets(widgets), mUiExecutor);
     }
 
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 312c6f4..269cb9f 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -568,7 +568,7 @@
     private void processFolderItems() {
         // Sort the folder items, update ranks, and make sure all preview items are high res.
         List<FolderGridOrganizer> verifiers = mApp.getInvariantDeviceProfile().supportedProfiles
-                .stream().map(FolderGridOrganizer::new).toList();
+                .stream().map(FolderGridOrganizer::createFolderGridOrganizer).toList();
         for (CollectionInfo collection : mBgDataModel.collections) {
             if (!(collection instanceof FolderInfo folder)) {
                 continue;
diff --git a/src/com/android/launcher3/model/ModelTaskController.kt b/src/com/android/launcher3/model/ModelTaskController.kt
index 266ed0c..cf2cadc 100644
--- a/src/com/android/launcher3/model/ModelTaskController.kt
+++ b/src/com/android/launcher3/model/ModelTaskController.kt
@@ -24,6 +24,7 @@
 import com.android.launcher3.model.data.ItemInfo
 import com.android.launcher3.model.data.WorkspaceItemInfo
 import com.android.launcher3.util.PackageUserKey
+import com.android.launcher3.widget.model.WidgetsListBaseEntriesBuilder
 import java.util.Objects
 import java.util.concurrent.Executor
 import java.util.function.Predicate
@@ -78,7 +79,9 @@
     }
 
     fun bindUpdatedWidgets(dataModel: BgDataModel) {
-        val widgets = dataModel.widgetsModel.getWidgetsListForPicker(app.context)
+        val widgets =
+            WidgetsListBaseEntriesBuilder(app.context)
+                .build(dataModel.widgetsModel.widgetsByPackageItem)
         scheduleCallbackTask { it.bindAllWidgets(widgets) }
     }
 
diff --git a/src/com/android/launcher3/model/WidgetsModel.java b/src/com/android/launcher3/model/WidgetsModel.java
index 58ebf0f..35064cf 100644
--- a/src/com/android/launcher3/model/WidgetsModel.java
+++ b/src/com/android/launcher3/model/WidgetsModel.java
@@ -26,7 +26,6 @@
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.compat.AlphabeticIndexCompat;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.icons.ComponentWithLabelAndIcon;
 import com.android.launcher3.icons.IconCache;
@@ -39,9 +38,6 @@
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.widget.WidgetManagerHelper;
 import com.android.launcher3.widget.WidgetSections;
-import com.android.launcher3.widget.model.WidgetsListBaseEntry;
-import com.android.launcher3.widget.model.WidgetsListContentEntry;
-import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
 import com.android.wm.shell.Flags;
 
 import java.util.ArrayList;
@@ -75,6 +71,9 @@
      * Returns all widgets keyed by their component key.
      */
     public synchronized Map<ComponentKey, WidgetItem> getWidgetsByComponentKey() {
+        if (!WIDGETS_ENABLED) {
+            return Collections.emptyMap();
+        }
         return mWidgetsByPackageItem.values().stream()
                 .flatMap(Collection::stream).distinct()
                 .collect(Collectors.toMap(
@@ -87,51 +86,10 @@
      * Returns widgets grouped by the package item that they should belong to.
      */
     public synchronized Map<PackageItemInfo, List<WidgetItem>> getWidgetsByPackageItem() {
-        return mWidgetsByPackageItem;
-    }
-
-    /**
-     * Returns a list of {@link WidgetsListBaseEntry} filtered using given widget item filter. All
-     * {@link WidgetItem}s in a single row are sorted (based on label and user), but the overall
-     * list of {@link WidgetsListBaseEntry}s is not sorted.
-     *
-     * @see com.android.launcher3.widget.picker.WidgetsListAdapter#setWidgets(List)
-     */
-    public synchronized ArrayList<WidgetsListBaseEntry> getFilteredWidgetsListForPicker(
-            Context context,
-            Predicate<WidgetItem> widgetItemFilter) {
         if (!WIDGETS_ENABLED) {
-            return new ArrayList<>();
+            return Collections.emptyMap();
         }
-        ArrayList<WidgetsListBaseEntry> result = new ArrayList<>();
-        AlphabeticIndexCompat indexer = new AlphabeticIndexCompat(context);
-
-        for (Map.Entry<PackageItemInfo, List<WidgetItem>> entry :
-                mWidgetsByPackageItem.entrySet()) {
-            PackageItemInfo pkgItem = entry.getKey();
-            List<WidgetItem> widgetItems = entry.getValue()
-                    .stream()
-                    .filter(widgetItemFilter).toList();
-            if (!widgetItems.isEmpty()) {
-                String sectionName = (pkgItem.title == null) ? "" :
-                        indexer.computeSectionName(pkgItem.title);
-                result.add(WidgetsListHeaderEntry.create(pkgItem, sectionName, widgetItems));
-                result.add(new WidgetsListContentEntry(pkgItem, sectionName, widgetItems));
-            }
-        }
-        return result;
-    }
-
-    /**
-     * Returns a list of {@link WidgetsListBaseEntry}. All {@link WidgetItem} in a single row
-     * are sorted (based on label and user), but the overall list of
-     * {@link WidgetsListBaseEntry}s is not sorted.
-     *
-     * @see com.android.launcher3.widget.picker.WidgetsListAdapter#setWidgets(List)
-     */
-    public synchronized ArrayList<WidgetsListBaseEntry> getWidgetsListForPicker(Context context) {
-        // return all items
-        return getFilteredWidgetsListForPicker(context, /*widgetItemFilter=*/ item -> true);
+        return mWidgetsByPackageItem;
     }
 
     /**
diff --git a/src/com/android/launcher3/popup/PopupDataProvider.java b/src/com/android/launcher3/popup/PopupDataProvider.java
index fb463f7..7e139c3 100644
--- a/src/com/android/launcher3/popup/PopupDataProvider.java
+++ b/src/com/android/launcher3/popup/PopupDataProvider.java
@@ -64,6 +64,11 @@
 
     /** All installed widgets. */
     private List<WidgetsListBaseEntry> mAllWidgets = List.of();
+    /**
+     * Selectively chosen installed widgets which may be preferred for default display over the list
+     * of all widgets.
+     */
+    private List<WidgetsListBaseEntry> mDefaultWidgets = List.of();
     /** Widgets that can be recommended to the users. */
     private List<ItemInfo> mRecommendedWidgets = List.of();
 
@@ -194,6 +199,18 @@
 
     public void setAllWidgets(List<WidgetsListBaseEntry> allWidgets) {
         mAllWidgets = allWidgets;
+        mDefaultWidgets = List.of();
+        mChangeListener.onWidgetsBound();
+    }
+
+    /**
+     * Sets the list of widgets to be displayed by default and a complete list that can be displayed
+     * when user chooses to show all widgets.
+     */
+    public void setAllWidgets(List<WidgetsListBaseEntry> allWidgets,
+            List<WidgetsListBaseEntry> defaultWidgets) {
+        mAllWidgets = allWidgets;
+        mDefaultWidgets = defaultWidgets;
         mChangeListener.onWidgetsBound();
     }
 
@@ -205,6 +222,14 @@
         return mAllWidgets;
     }
 
+    /**
+     * Returns a "selectively" chosen list of widgets that may be preferred to be shown by default
+     * instead of a complete list.
+     */
+    public List<WidgetsListBaseEntry> getDefaultWidgets() {
+        return mDefaultWidgets;
+    }
+
     /** Returns a list of recommended widgets. */
     public List<WidgetItem> getRecommendedWidgets() {
         HashMap<ComponentKey, WidgetItem> allWidgetItems = new HashMap<>();
@@ -259,8 +284,10 @@
     }
 
     /** Gets the WidgetsListContentEntry for the currently selected header. */
-    public WidgetsListContentEntry getSelectedAppWidgets(PackageUserKey packageUserKey) {
-        return (WidgetsListContentEntry) mAllWidgets.stream()
+    public WidgetsListContentEntry getSelectedAppWidgets(PackageUserKey packageUserKey,
+            boolean useDefault) {
+        List<WidgetsListBaseEntry> widgets = useDefault ? mDefaultWidgets : mAllWidgets;
+        return (WidgetsListContentEntry) widgets.stream()
                 .filter(row -> row instanceof WidgetsListContentEntry
                         && PackageUserKey.fromPackageItemInfo(row.mPkgItem).equals(packageUserKey))
                 .findAny()
diff --git a/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt b/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt
index 6d6b3b6..f895b30 100644
--- a/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt
+++ b/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt
@@ -17,6 +17,9 @@
 package com.android.launcher3.recyclerview
 
 import android.content.Context
+import android.view.ViewGroup
+import androidx.annotation.VisibleForTesting
+import androidx.annotation.VisibleForTesting.Companion.PROTECTED
 import androidx.recyclerview.widget.RecyclerView
 import androidx.recyclerview.widget.RecyclerView.RecycledViewPool
 import androidx.recyclerview.widget.RecyclerView.ViewHolder
@@ -38,21 +41,18 @@
  * [RecyclerView]. The view inflation will happen on background thread and inflated [ViewHolder]s
  * will be added to [RecycledViewPool] on main thread.
  */
-class AllAppsRecyclerViewPool<T> : RecycledViewPool() {
+class AllAppsRecyclerViewPool<T> : RecycledViewPool() where T : Context, T : ActivityContext {
 
     var hasWorkProfile = false
-    private var mCancellableTask: CancellableTask<List<ViewHolder>>? = null
+    @VisibleForTesting(otherwise = PROTECTED)
+    var mCancellableTask: CancellableTask<List<ViewHolder>>? = null
 
     /**
      * Preinflate app icons. If all apps RV cannot be scrolled down, we don't need to preinflate.
      */
-    fun <T> preInflateAllAppsViewHolders(context: T) where T : Context, T : ActivityContext {
+    fun preInflateAllAppsViewHolders(context: T) {
         val appsView = context.appsView ?: return
         val activeRv: RecyclerView = appsView.activeRecyclerView ?: return
-        val preInflateCount = getPreinflateCount(context)
-        if (preInflateCount <= 0) {
-            return
-        }
 
         // Create a separate context dedicated for all apps preinflation thread. The goal is to
         // create a separate AssetManager obj internally to avoid lock contention with
@@ -77,34 +77,51 @@
                     null
                 ) {
                 override fun setAppsPerRow(appsPerRow: Int) = Unit
+
                 override fun getLayoutManager(): RecyclerView.LayoutManager? = null
             }
 
+        preInflateAllAppsViewHolders(adapter, BaseAllAppsAdapter.VIEW_TYPE_ICON, activeRv) {
+            getPreinflateCount(context)
+        }
+    }
+
+    @VisibleForTesting(otherwise = PROTECTED)
+    fun preInflateAllAppsViewHolders(
+        adapter: RecyclerView.Adapter<*>,
+        viewType: Int,
+        parent: ViewGroup,
+        preInflationCountProvider: () -> Int
+    ) {
+        val preinflationCount = preInflationCountProvider.invoke()
+        if (preinflationCount <= 0) {
+            return
+        }
         mCancellableTask?.cancel()
         var task: CancellableTask<List<ViewHolder>>? = null
         task =
             CancellableTask(
                 {
                     val list: ArrayList<ViewHolder> = ArrayList()
-                    for (i in 0 until preInflateCount) {
+                    for (i in 0 until preinflationCount) {
                         if (task?.canceled == true) {
                             break
                         }
-                        list.add(
-                            adapter.createViewHolder(activeRv, BaseAllAppsAdapter.VIEW_TYPE_ICON)
-                        )
+                        list.add(adapter.createViewHolder(parent, viewType))
                     }
                     list
                 },
                 MAIN_EXECUTOR,
                 { viewHolders ->
-                    for (i in 0 until minOf(viewHolders.size, getPreinflateCount(context))) {
+                    // Run preInflationCountProvider again as the needed VH might have changed
+                    val newPreinflationCount = preInflationCountProvider.invoke()
+                    for (i in 0 until minOf(viewHolders.size, newPreinflationCount)) {
                         putRecycledView(viewHolders[i])
                     }
                 }
             )
         mCancellableTask = task
-        VIEW_PREINFLATION_EXECUTOR.submit(mCancellableTask)
+        VIEW_PREINFLATION_EXECUTOR.execute(mCancellableTask)
     }
 
     /**
@@ -125,7 +142,7 @@
      * app icons in size of one all apps pages, so that opening all apps don't need to inflate app
      * icons.
      */
-    fun <T> getPreinflateCount(context: T): Int where T : Context, T : ActivityContext {
+    fun getPreinflateCount(context: T): Int {
         var targetPreinflateCount =
             PREINFLATE_ICONS_ROW_COUNT * context.deviceProfile.numShownAllAppsColumns +
                 EXTRA_ICONS_COUNT
diff --git a/src/com/android/launcher3/util/ScreenOnTracker.java b/src/com/android/launcher3/util/ScreenOnTracker.java
index 12eff61..8ee799a 100644
--- a/src/com/android/launcher3/util/ScreenOnTracker.java
+++ b/src/com/android/launcher3/util/ScreenOnTracker.java
@@ -24,7 +24,10 @@
 import android.content.Context;
 import android.content.Intent;
 
+import androidx.annotation.VisibleForTesting;
+
 import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.Consumer;
 
 /**
  * Utility class for tracking if the screen is currently on or off
@@ -34,8 +37,7 @@
     public static final MainThreadInitializedObject<ScreenOnTracker> INSTANCE =
             new MainThreadInitializedObject<>(ScreenOnTracker::new);
 
-    private final SimpleBroadcastReceiver mReceiver =
-            new SimpleBroadcastReceiver(UI_HELPER_EXECUTOR, this::onReceive);
+    private final SimpleBroadcastReceiver mReceiver;
     private final CopyOnWriteArrayList<ScreenOnListener> mListeners = new CopyOnWriteArrayList<>();
 
     private final Context mContext;
@@ -44,8 +46,20 @@
     private ScreenOnTracker(Context context) {
         // Assume that the screen is on to begin with
         mContext = context;
+        mReceiver = new SimpleBroadcastReceiver(UI_HELPER_EXECUTOR, this::onReceive);
+        init();
+    }
+
+    @VisibleForTesting
+    ScreenOnTracker(Context context, SimpleBroadcastReceiver receiver) {
+        mContext = context;
+        mReceiver = receiver;
+        init();
+    }
+
+    private void init() {
         mIsScreenOn = true;
-        mReceiver.register(context, ACTION_SCREEN_ON, ACTION_SCREEN_OFF, ACTION_USER_PRESENT);
+        mReceiver.register(mContext, ACTION_SCREEN_ON, ACTION_SCREEN_OFF, ACTION_USER_PRESENT);
     }
 
     @Override
@@ -53,7 +67,8 @@
         mReceiver.unregisterReceiverSafely(mContext);
     }
 
-    private void onReceive(Intent intent) {
+    @VisibleForTesting
+    void onReceive(Intent intent) {
         String action = intent.getAction();
         if (ACTION_SCREEN_ON.equals(action)) {
             mIsScreenOn = true;
diff --git a/src/com/android/launcher3/util/ViewPool.java b/src/com/android/launcher3/util/ViewPool.java
index e413d7f..2fa8bf4 100644
--- a/src/com/android/launcher3/util/ViewPool.java
+++ b/src/com/android/launcher3/util/ViewPool.java
@@ -24,6 +24,7 @@
 import androidx.annotation.AnyThread;
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
+import androidx.annotation.VisibleForTesting;
 
 import com.android.launcher3.util.ViewPool.Reusable;
 
@@ -43,9 +44,16 @@
 
     public ViewPool(Context context, @Nullable ViewGroup parent,
             int layoutId, int maxSize, int initialSize) {
+        this(LayoutInflater.from(context).cloneInContext(context),
+                parent, layoutId, maxSize, initialSize);
+    }
+
+    @VisibleForTesting
+    ViewPool(LayoutInflater inflater, @Nullable ViewGroup parent,
+            int layoutId, int maxSize, int initialSize) {
         mLayoutId = layoutId;
         mParent = parent;
-        mInflater = LayoutInflater.from(context);
+        mInflater = inflater;
         mPool = new Object[maxSize];
 
         if (initialSize > 0) {
diff --git a/src/com/android/launcher3/widget/BaseLauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/BaseLauncherAppWidgetHostView.java
index 104209e..856f4b3 100644
--- a/src/com/android/launcher3/widget/BaseLauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/BaseLauncherAppWidgetHostView.java
@@ -110,7 +110,7 @@
         }
         View background = RoundedCornerEnforcement.findBackground(this);
         if (background == null
-                || RoundedCornerEnforcement.hasAppWidgetOptedOut(this, background)) {
+                || RoundedCornerEnforcement.hasAppWidgetOptedOut(background)) {
             resetRoundedCorners();
             return;
         }
diff --git a/src/com/android/launcher3/widget/RoundedCornerEnforcement.java b/src/com/android/launcher3/widget/RoundedCornerEnforcement.java
index 1e46ffd..2e5e251 100644
--- a/src/com/android/launcher3/widget/RoundedCornerEnforcement.java
+++ b/src/com/android/launcher3/widget/RoundedCornerEnforcement.java
@@ -67,7 +67,7 @@
     /**
      * Check whether the app widget has opted out of the enforcement.
      */
-    public static boolean hasAppWidgetOptedOut(@NonNull View appWidget, @NonNull View background) {
+    public static boolean hasAppWidgetOptedOut(@NonNull View background) {
         return background.getId() == android.R.id.background && background.getClipToOutline();
     }
 
diff --git a/src/com/android/launcher3/widget/custom/CustomAppWidgetProviderInfo.java b/src/com/android/launcher3/widget/custom/CustomAppWidgetProviderInfo.java
index 398b1df..5ad9222 100644
--- a/src/com/android/launcher3/widget/custom/CustomAppWidgetProviderInfo.java
+++ b/src/com/android/launcher3/widget/custom/CustomAppWidgetProviderInfo.java
@@ -22,6 +22,8 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import androidx.annotation.VisibleForTesting;
+
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
@@ -52,6 +54,9 @@
         }
     }
 
+    @VisibleForTesting
+    CustomAppWidgetProviderInfo() {}
+
     @Override
     public void initSpans(Context context, InvariantDeviceProfile idp) {
         mIsMinSizeFulfilled = Math.min(spanX, minSpanX) <= idp.numColumns
diff --git a/src/com/android/launcher3/widget/custom/CustomWidgetManager.java b/src/com/android/launcher3/widget/custom/CustomWidgetManager.java
index 50012b3..faa5d12 100644
--- a/src/com/android/launcher3/widget/custom/CustomWidgetManager.java
+++ b/src/com/android/launcher3/widget/custom/CustomWidgetManager.java
@@ -30,6 +30,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 
 import com.android.launcher3.R;
 import com.android.launcher3.util.MainThreadInitializedObject;
@@ -45,6 +46,7 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.function.Consumer;
 import java.util.stream.Stream;
 
@@ -62,9 +64,16 @@
     private final HashMap<ComponentName, CustomWidgetPlugin> mPlugins;
     private final List<CustomAppWidgetProviderInfo> mCustomWidgets;
     private Consumer<PackageUserKey> mWidgetRefreshCallback;
+    private final @NonNull AppWidgetManager mAppWidgetManager;
 
     private CustomWidgetManager(Context context) {
+        this(context, AppWidgetManager.getInstance(context));
+    }
+
+    @VisibleForTesting
+    CustomWidgetManager(Context context, @NonNull AppWidgetManager widgetManager) {
         mContext = context;
+        mAppWidgetManager = widgetManager;
         mPlugins = new HashMap<>();
         mCustomWidgets = new ArrayList<>();
         PluginManagerWrapper.INSTANCE.get(context)
@@ -94,7 +103,7 @@
 
     @Override
     public void onPluginConnected(CustomWidgetPlugin plugin, Context context) {
-        List<AppWidgetProviderInfo> providers = AppWidgetManager.getInstance(context)
+        List<AppWidgetProviderInfo> providers = mAppWidgetManager
                 .getInstalledProvidersForProfile(Process.myUserHandle());
         if (providers.isEmpty()) return;
         Parcel parcel = Parcel.obtain();
@@ -113,6 +122,12 @@
         mCustomWidgets.removeIf(w -> w.getComponent().equals(cn));
     }
 
+    @VisibleForTesting
+    @NonNull
+    Map<ComponentName, CustomWidgetPlugin> getPlugins() {
+        return mPlugins;
+    }
+
     /**
      * Inject a callback function to refresh the widgets.
      */
diff --git a/src/com/android/launcher3/widget/model/WidgetsListBaseEntriesBuilder.kt b/src/com/android/launcher3/widget/model/WidgetsListBaseEntriesBuilder.kt
new file mode 100644
index 0000000..1abe4da
--- /dev/null
+++ b/src/com/android/launcher3/widget/model/WidgetsListBaseEntriesBuilder.kt
@@ -0,0 +1,51 @@
+/*
+ * 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.widget.model
+
+import android.content.Context
+import com.android.launcher3.compat.AlphabeticIndexCompat
+import com.android.launcher3.model.WidgetItem
+import com.android.launcher3.model.data.PackageItemInfo
+import java.util.function.Predicate
+
+/**
+ * A helper class that builds the list of [WidgetsListBaseEntry]s used by the UI to display widgets.
+ */
+class WidgetsListBaseEntriesBuilder(val context: Context) {
+
+    /** Builds the widgets list entries in a format understandable by the widget picking UI. */
+    @JvmOverloads
+    fun build(
+        widgetsByPackageItem: Map<PackageItemInfo, List<WidgetItem>>,
+        widgetFilter: Predicate<WidgetItem> = Predicate<WidgetItem> { true },
+    ): List<WidgetsListBaseEntry> {
+        val indexer = AlphabeticIndexCompat(context)
+
+        return buildList {
+            for ((pkgItem, widgetItems) in widgetsByPackageItem.entries) {
+                val filteredWidgetItems = widgetItems.filter { widgetFilter.test(it) }
+                if (filteredWidgetItems.isNotEmpty()) {
+                    // Enables fast scroll popup to show right characters in all locales.
+                    val sectionName = pkgItem.title?.let { indexer.computeSectionName(it) } ?: ""
+
+                    add(WidgetsListHeaderEntry.create(pkgItem, sectionName, filteredWidgetItems))
+                    add(WidgetsListContentEntry(pkgItem, sectionName, filteredWidgetItems))
+                }
+            }
+        }
+    }
+}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index 2e36583..21b7be4 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -69,6 +69,7 @@
 import com.android.launcher3.widget.model.WidgetsListBaseEntry;
 import com.android.launcher3.widget.picker.search.SearchModeListener;
 import com.android.launcher3.widget.picker.search.WidgetsSearchBar;
+import com.android.launcher3.widget.picker.search.WidgetsSearchBar.WidgetsSearchDataProvider;
 import com.android.launcher3.workprofile.PersonalWorkPagedView;
 import com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip.OnActivePageChangedListener;
 
@@ -135,7 +136,7 @@
     private WidgetsRecyclerView mCurrentTouchEventRecyclerView;
     @Nullable
     PersonalWorkPagedView mViewPager;
-    private boolean mIsInSearchMode;
+    protected boolean mIsInSearchMode;
     private boolean mIsNoWidgetsViewNeeded;
     @Px
     protected int mMaxSpanPerRow;
@@ -245,8 +246,12 @@
         mSearchBarContainer = mSearchScrollView.findViewById(R.id.search_bar_container);
         mSearchBar = mSearchScrollView.findViewById(R.id.widgets_search_bar);
 
-        mSearchBar.initialize(
-                mActivityContext.getPopupDataProvider(), /* searchModeListener= */ this);
+        mSearchBar.initialize(new WidgetsSearchDataProvider() {
+            @Override
+            public List<WidgetsListBaseEntry> getWidgets() {
+                return getWidgetsToDisplay();
+            }
+        }, /* searchModeListener= */ this);
     }
 
     private void setDeviceManagementResources() {
@@ -462,22 +467,28 @@
         setTranslationShift(mTranslationShift);
     }
 
+    /**
+     * Returns all displayable widgets.
+     */
+    protected List<WidgetsListBaseEntry> getWidgetsToDisplay() {
+        return mActivityContext.getPopupDataProvider().getAllWidgets();
+    }
+
     @Override
     public void onWidgetsBound() {
         if (mIsInSearchMode) {
             return;
         }
-        List<WidgetsListBaseEntry> allWidgets =
-                mActivityContext.getPopupDataProvider().getAllWidgets();
+        List<WidgetsListBaseEntry> widgets = getWidgetsToDisplay();
 
         AdapterHolder primaryUserAdapterHolder = mAdapters.get(AdapterHolder.PRIMARY);
-        primaryUserAdapterHolder.mWidgetsListAdapter.setWidgets(allWidgets);
+        primaryUserAdapterHolder.mWidgetsListAdapter.setWidgets(widgets);
 
         if (mHasWorkProfile) {
             mViewPager.setVisibility(VISIBLE);
             mTabBar.setVisibility(VISIBLE);
             AdapterHolder workUserAdapterHolder = mAdapters.get(AdapterHolder.WORK);
-            workUserAdapterHolder.mWidgetsListAdapter.setWidgets(allWidgets);
+            workUserAdapterHolder.mWidgetsListAdapter.setWidgets(widgets);
             onActivePageChanged(mViewPager.getCurrentPage());
         } else {
             onActivePageChanged(0);
diff --git a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
index c84680d..c4c755a 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
@@ -27,7 +27,9 @@
 import android.graphics.Rect;
 import android.os.Process;
 import android.util.AttributeSet;
+import android.view.Gravity;
 import android.view.LayoutInflater;
+import android.view.MenuItem;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
@@ -35,6 +37,7 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.FrameLayout;
 import android.widget.LinearLayout;
+import android.widget.PopupMenu;
 import android.widget.ScrollView;
 import android.widget.TextView;
 
@@ -83,6 +86,17 @@
     private PackageUserKey mSelectedHeader;
     private TextView mHeaderDescription;
 
+    /**
+     * A menu displayed for options (e.g. "show all widgets" filter) around widget lists in the
+     * picker.
+     */
+    protected View mWidgetOptionsMenu;
+    /**
+     * State of the options in the menu (if displayed to the user).
+     */
+    @Nullable
+    protected WidgetOptionsMenuState mWidgetOptionsMenuState = null;
+
     public WidgetsTwoPaneSheet(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
     }
@@ -130,6 +144,9 @@
         mHeaderTitle = mContent.findViewById(R.id.title);
         mHeaderDescription = mContent.findViewById(R.id.widget_picker_description);
 
+        mWidgetOptionsMenu = mContent.findViewById(R.id.widget_picker_widget_options_menu);
+        setupWidgetOptionsMenu();
+
         mRightPane = mContent.findViewById(R.id.right_pane);
         mRightPaneScrollView = mContent.findViewById(R.id.right_pane_scroll_view);
         mRightPaneScrollView.setOverScrollMode(View.OVER_SCROLL_NEVER);
@@ -155,6 +172,40 @@
         }
     }
 
+    protected void setupWidgetOptionsMenu() {
+        mWidgetOptionsMenu.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (mWidgetOptionsMenuState != null) {
+                    PopupMenu popupMenu = new PopupMenu(mActivityContext, /*anchor=*/ v,
+                            Gravity.END);
+                    MenuItem menuItem = popupMenu.getMenu().add(
+                            R.string.widget_picker_show_all_widgets_menu_item_title);
+                    menuItem.setCheckable(true);
+                    menuItem.setChecked(mWidgetOptionsMenuState.showAllWidgets);
+                    menuItem.setOnMenuItemClickListener(
+                            item -> onShowAllWidgetsMenuItemClick(item));
+                    popupMenu.show();
+                }
+            }
+        });
+    }
+
+    private boolean onShowAllWidgetsMenuItemClick(MenuItem menuItem) {
+        mWidgetOptionsMenuState.showAllWidgets = !mWidgetOptionsMenuState.showAllWidgets;
+        menuItem.setChecked(mWidgetOptionsMenuState.showAllWidgets);
+
+        // Refresh widgets
+        onWidgetsBound();
+        if (mIsInSearchMode) {
+            mSearchBar.reset();
+        } else if (!mSuggestedWidgetsPackageUserKey.equals(mSelectedHeader)) {
+            mAdapters.get(mActivePage).mWidgetsListAdapter.selectFirstHeaderEntry();
+            mAdapters.get(mActivePage).mWidgetsRecyclerView.scrollToTop();
+        }
+        return true;
+    }
+
     @Override
     protected int getTabletHorizontalMargin(DeviceProfile deviceProfile) {
         if (enableCategorizedWidgetSuggestions()) {
@@ -234,6 +285,29 @@
     }
 
     @Override
+    protected List<WidgetsListBaseEntry> getWidgetsToDisplay() {
+        List<WidgetsListBaseEntry> allWidgets =
+                mActivityContext.getPopupDataProvider().getAllWidgets();
+        List<WidgetsListBaseEntry> defaultWidgets =
+                mActivityContext.getPopupDataProvider().getDefaultWidgets();
+
+        if (allWidgets.isEmpty() || defaultWidgets.isEmpty()) {
+            // no menu if there are no default widgets to show
+            mWidgetOptionsMenuState = null;
+            mWidgetOptionsMenu.setVisibility(GONE);
+        } else {
+            if (mWidgetOptionsMenuState == null) {
+                mWidgetOptionsMenuState = new WidgetOptionsMenuState();
+            }
+
+            mWidgetOptionsMenu.setVisibility(VISIBLE);
+            return mWidgetOptionsMenuState.showAllWidgets ? allWidgets : defaultWidgets;
+        }
+
+        return allWidgets;
+    }
+
+    @Override
     public void onWidgetsBound() {
         super.onWidgetsBound();
         if (mRecommendedWidgetsCount == 0 && mSelectedHeader == null) {
@@ -435,8 +509,11 @@
                 final boolean isUserClick = mSelectedHeader != null
                         && !getAccessibilityInitialFocusView().isAccessibilityFocused();
                 mSelectedHeader = selectedHeader;
-                WidgetsListContentEntry contentEntry = mActivityContext.getPopupDataProvider()
-                        .getSelectedAppWidgets(selectedHeader);
+                WidgetsListContentEntry contentEntry =
+                        mActivityContext.getPopupDataProvider().getSelectedAppWidgets(
+                                selectedHeader, /*useDefault=*/
+                                (mWidgetOptionsMenuState != null
+                                        && !mWidgetOptionsMenuState.showAllWidgets));
 
                 if (contentEntry == null || mRightPane == null) {
                     return;
@@ -570,4 +647,15 @@
          */
         void onHeaderChanged(@NonNull PackageUserKey selectedHeader);
     }
+
+    /**
+     * Holds the selection state of the options menu (if presented to the user).
+     */
+    protected static class WidgetOptionsMenuState {
+        /**
+         * UI state indicating whether to show default or all widgets.
+         * <p>If true, shows all widgets; else shows the default widgets.</p>
+         */
+        public boolean showAllWidgets = false;
+    }
 }
diff --git a/src/com/android/launcher3/widget/picker/search/LauncherWidgetsSearchBar.java b/src/com/android/launcher3/widget/picker/search/LauncherWidgetsSearchBar.java
index 65937b6..92caf3e 100644
--- a/src/com/android/launcher3/widget/picker/search/LauncherWidgetsSearchBar.java
+++ b/src/com/android/launcher3/widget/picker/search/LauncherWidgetsSearchBar.java
@@ -26,7 +26,6 @@
 
 import com.android.launcher3.ExtendedEditText;
 import com.android.launcher3.R;
-import com.android.launcher3.popup.PopupDataProvider;
 
 /**
  * View for a search bar with an edit text with a cancel button.
@@ -51,7 +50,8 @@
     }
 
     @Override
-    public void initialize(PopupDataProvider dataProvider, SearchModeListener searchModeListener) {
+    public void initialize(WidgetsSearchDataProvider dataProvider,
+            SearchModeListener searchModeListener) {
         mController = new WidgetsSearchBarController(
                 new SimpleWidgetsSearchAlgorithm(dataProvider),
                 mEditText, mCancelButton, searchModeListener);
diff --git a/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithm.java b/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithm.java
index 613066a..0e88787 100644
--- a/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithm.java
+++ b/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithm.java
@@ -21,13 +21,13 @@
 import android.os.Handler;
 
 import com.android.launcher3.model.WidgetItem;
-import com.android.launcher3.popup.PopupDataProvider;
 import com.android.launcher3.search.SearchAlgorithm;
 import com.android.launcher3.search.SearchCallback;
 import com.android.launcher3.search.StringMatcherUtility.StringMatcher;
 import com.android.launcher3.widget.model.WidgetsListBaseEntry;
 import com.android.launcher3.widget.model.WidgetsListContentEntry;
 import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
+import com.android.launcher3.widget.picker.search.WidgetsSearchBar.WidgetsSearchDataProvider;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -39,9 +39,9 @@
 public final class SimpleWidgetsSearchAlgorithm implements SearchAlgorithm<WidgetsListBaseEntry> {
 
     private final Handler mResultHandler;
-    private final PopupDataProvider mDataProvider;
+    private final WidgetsSearchDataProvider mDataProvider;
 
-    public SimpleWidgetsSearchAlgorithm(PopupDataProvider dataProvider) {
+    public SimpleWidgetsSearchAlgorithm(WidgetsSearchDataProvider dataProvider) {
         mResultHandler = new Handler();
         mDataProvider = dataProvider;
     }
@@ -63,9 +63,9 @@
      * Returns entries for all matched widgets
      */
     public static ArrayList<WidgetsListBaseEntry> getFilteredWidgets(
-            PopupDataProvider dataProvider, String input) {
+            WidgetsSearchDataProvider dataProvider, String input) {
         ArrayList<WidgetsListBaseEntry> results = new ArrayList<>();
-        dataProvider.getAllWidgets().stream()
+        dataProvider.getWidgets().stream()
                 .filter(entry -> entry instanceof WidgetsListHeaderEntry)
                 .forEach(headerEntry -> {
                     List<WidgetItem> matchedWidgetItems = filterWidgetItems(
diff --git a/src/com/android/launcher3/widget/picker/search/WidgetsSearchBar.java b/src/com/android/launcher3/widget/picker/search/WidgetsSearchBar.java
index 44a5e80..ab504e7 100644
--- a/src/com/android/launcher3/widget/picker/search/WidgetsSearchBar.java
+++ b/src/com/android/launcher3/widget/picker/search/WidgetsSearchBar.java
@@ -16,7 +16,9 @@
 
 package com.android.launcher3.widget.picker.search;
 
-import com.android.launcher3.popup.PopupDataProvider;
+import com.android.launcher3.widget.model.WidgetsListBaseEntry;
+
+import java.util.List;
 
 /**
  * Interface for a widgets picker search bar.
@@ -25,7 +27,7 @@
     /**
      * Attaches a controller to the search bar which interacts with {@code searchModeListener}.
      */
-    void initialize(PopupDataProvider dataProvider, SearchModeListener searchModeListener);
+    void initialize(WidgetsSearchDataProvider dataProvider, SearchModeListener searchModeListener);
 
     /**
      * Clears search bar.
@@ -44,4 +46,15 @@
      * Sets the vertical location, in pixels, of this search bar relative to its top position.
      */
     void setTranslationY(float translationY);
+
+
+    /**
+     * Provides corpus from which search results must be returned.
+     */
+    interface WidgetsSearchDataProvider {
+        /**
+         * Returns the widgets from which the search should return the results.
+         */
+        List<WidgetsListBaseEntry> getWidgets();
+    }
 }
diff --git a/tests/multivalentTests/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapterTest.kt b/tests/multivalentTests/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapterTest.kt
new file mode 100644
index 0000000..e03ee46
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapterTest.kt
@@ -0,0 +1,110 @@
+/*
+ * 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.accessibility
+
+import android.content.Context
+import android.view.ViewGroup
+import androidx.test.core.app.ApplicationProvider.getApplicationContext
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.CellLayout
+import com.android.launcher3.DropTarget.DragObject
+import com.android.launcher3.dragndrop.DragOptions
+import com.android.launcher3.util.ActivityContextWrapper
+import java.util.function.Function
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.kotlin.any
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AccessibleDragListenerAdapterTest {
+
+    private lateinit var mockViewGroup: ViewGroup
+    private lateinit var mockChildOne: CellLayout
+    private lateinit var mockChildTwo: CellLayout
+    private lateinit var mDelegateFactory: Function<CellLayout, DragAndDropAccessibilityDelegate>
+    private lateinit var adapter: AccessibleDragListenerAdapter
+    private lateinit var mContext: Context
+
+    @Before
+    fun setup() {
+        mContext = ActivityContextWrapper(getApplicationContext())
+        mockViewGroup = mock(ViewGroup::class.java)
+        mockChildOne = mock(CellLayout::class.java)
+        mockChildTwo = mock(CellLayout::class.java)
+        `when`(mockViewGroup.context).thenReturn(mContext)
+        `when`(mockViewGroup.childCount).thenReturn(2)
+        `when`(mockViewGroup.getChildAt(0)).thenReturn(mockChildOne)
+        `when`(mockViewGroup.getChildAt(1)).thenReturn(mockChildTwo)
+        // Mock Delegate factory
+        mDelegateFactory =
+            mock(Function::class.java) as Function<CellLayout, DragAndDropAccessibilityDelegate>
+        `when`(mDelegateFactory.apply(any()))
+            .thenReturn(mock(DragAndDropAccessibilityDelegate::class.java))
+        adapter = AccessibleDragListenerAdapter(mockViewGroup, mDelegateFactory)
+    }
+
+    @Test
+    fun `onDragStart enables accessible drag for all view children`() {
+        // Create mock view children
+        val mockDragObject = mock(DragObject::class.java)
+        val mockDragOptions = mock(DragOptions::class.java)
+
+        // Action
+        adapter.onDragStart(mockDragObject, mockDragOptions)
+
+        // Assertion
+        verify(mockChildOne).setDragAndDropAccessibilityDelegate(any())
+        verify(mockChildTwo).setDragAndDropAccessibilityDelegate(any())
+    }
+
+    @Test
+    fun `onDragEnd removes the accessibility delegate`() {
+        // Action
+        adapter = AccessibleDragListenerAdapter(mockViewGroup, mDelegateFactory)
+        adapter.onDragEnd()
+
+        // Assertion
+        verify(mockChildOne).setDragAndDropAccessibilityDelegate(null)
+        verify(mockChildTwo).setDragAndDropAccessibilityDelegate(null)
+    }
+
+    @Test
+    fun `onChildViewAdded sets enabled as true for childview`() {
+        // Action
+        adapter = AccessibleDragListenerAdapter(mockViewGroup, mDelegateFactory)
+        adapter.onChildViewAdded(mockViewGroup, mockChildOne)
+
+        // Assertion
+        verify(mockChildOne).setDragAndDropAccessibilityDelegate(any())
+    }
+
+    @Test
+    fun `onChildViewRemoved sets enabled as false for childview`() {
+        // Action
+        adapter = AccessibleDragListenerAdapter(mockViewGroup, mDelegateFactory)
+        adapter.onChildViewRemoved(mockViewGroup, mockChildOne)
+
+        // Assertion
+        verify(mockChildOne).setDragAndDropAccessibilityDelegate(null)
+    }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPoolTest.kt b/tests/multivalentTests/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPoolTest.kt
new file mode 100644
index 0000000..8204313
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPoolTest.kt
@@ -0,0 +1,116 @@
+/*
+ * 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.recyclerview
+
+import android.content.Context
+import android.view.View
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+import androidx.recyclerview.widget.RecyclerView.ViewHolder
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.util.Executors
+import com.android.launcher3.views.ActivityContext
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AllAppsRecyclerViewPoolTest<T> where T : Context, T : ActivityContext {
+
+    private lateinit var underTest: AllAppsRecyclerViewPool<T>
+    private lateinit var adapter: RecyclerView.Adapter<*>
+
+    @Mock private lateinit var parent: ViewGroup
+    @Mock private lateinit var itemView: View
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        underTest = spy(AllAppsRecyclerViewPool())
+        adapter =
+            object : RecyclerView.Adapter<ViewHolder>() {
+                override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
+                    object : ViewHolder(itemView) {}
+
+                override fun getItemCount() = 0
+
+                override fun onBindViewHolder(holder: ViewHolder, position: Int) {}
+            }
+        underTest.setMaxRecycledViews(VIEW_TYPE, 20)
+    }
+
+    @Test
+    fun preinflate_success() {
+        underTest.preInflateAllAppsViewHolders(adapter, VIEW_TYPE, parent) { 10 }
+
+        awaitTasksCompleted()
+        assertThat(underTest.getRecycledViewCount(VIEW_TYPE)).isEqualTo(10)
+    }
+
+    @Test
+    fun preinflate_not_triggered() {
+        underTest.preInflateAllAppsViewHolders(adapter, VIEW_TYPE, parent) { 0 }
+
+        awaitTasksCompleted()
+        assertThat(underTest.getRecycledViewCount(VIEW_TYPE)).isEqualTo(0)
+    }
+
+    @Test
+    fun preinflate_cancel_before_runOnMainThread() {
+        underTest.preInflateAllAppsViewHolders(adapter, VIEW_TYPE, parent) { 10 }
+        assertThat(underTest.mCancellableTask!!.canceled).isFalse()
+
+        underTest.clear()
+
+        awaitTasksCompleted()
+        verify(underTest, never()).putRecycledView(any(ViewHolder::class.java))
+        assertThat(underTest.mCancellableTask!!.canceled).isTrue()
+        assertThat(underTest.getRecycledViewCount(VIEW_TYPE)).isEqualTo(0)
+    }
+
+    @Test
+    fun preinflate_cancel_after_run() {
+        underTest.preInflateAllAppsViewHolders(adapter, VIEW_TYPE, parent) { 10 }
+        assertThat(underTest.mCancellableTask!!.canceled).isFalse()
+        awaitTasksCompleted()
+
+        underTest.clear()
+
+        verify(underTest, times(10)).putRecycledView(any(ViewHolder::class.java))
+        assertThat(underTest.mCancellableTask!!.canceled).isTrue()
+        assertThat(underTest.getRecycledViewCount(VIEW_TYPE)).isEqualTo(0)
+    }
+
+    private fun awaitTasksCompleted() {
+        Executors.VIEW_PREINFLATION_EXECUTOR.submit<Any> { null }.get()
+        Executors.MAIN_EXECUTOR.submit<Any> { null }.get()
+    }
+
+    companion object {
+        private const val VIEW_TYPE: Int = 4
+    }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/ActivityContextWrapper.java b/tests/multivalentTests/src/com/android/launcher3/util/ActivityContextWrapper.java
index 191d284..4217d22 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/ActivityContextWrapper.java
+++ b/tests/multivalentTests/src/com/android/launcher3/util/ActivityContextWrapper.java
@@ -15,6 +15,7 @@
  */
 package com.android.launcher3.util;
 
+import android.R;
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.view.ContextThemeWrapper;
@@ -39,11 +40,16 @@
     private final MyDragLayer mMyDragLayer;
 
     public ActivityContextWrapper(Context base) {
-        super(base, android.R.style.Theme_DeviceDefault);
+        this(base, R.style.Theme_DeviceDefault);
+    }
+
+    public ActivityContextWrapper(Context base, int theme) {
+        super(base, theme);
         mProfile = InvariantDeviceProfile.INSTANCE.get(base).getDeviceProfile(base).copy(base);
         mMyDragLayer = new MyDragLayer(this);
     }
 
+
     @Override
     public BaseDragLayer getDragLayer() {
         return mMyDragLayer;
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/ItemInfoMatcherTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/ItemInfoMatcherTest.kt
new file mode 100644
index 0000000..daba024
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/util/ItemInfoMatcherTest.kt
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util
+
+import android.content.ComponentName
+import android.content.Intent
+import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
+import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER
+import com.android.launcher3.model.data.FolderInfo
+import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.shortcuts.ShortcutKey
+import com.android.launcher3.util.ItemInfoMatcher.ofShortcutKeys
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ItemInfoMatcherTest {
+
+    @Test
+    fun `ofUser returns Predicate for ItemInfo containing given UserHandle`() {
+        val expectedItemInfo = ItemInfo().apply { user = UserHandle(11) }
+        val unexpectedItemInfo = ItemInfo().apply { user = UserHandle(0) }
+        val itemInfoStream = listOf(expectedItemInfo, unexpectedItemInfo).stream()
+
+        val predicate = ItemInfoMatcher.ofUser(UserHandle(11))
+        val actualResults = itemInfoStream.filter(predicate).toList()
+
+        assertThat(actualResults).containsExactly(expectedItemInfo)
+    }
+
+    @Test
+    fun `ofComponents returns Predicate for ItemInfo containing target Component and UserHandle`() {
+        // Given
+        val expectedUserHandle = UserHandle(0)
+        val expectedComponentName = ComponentName("expectedPackage", "expectedClass")
+        val expectedItemInfo = spy(ItemInfo())
+        expectedItemInfo.user = expectedUserHandle
+        whenever(expectedItemInfo.targetComponent).thenReturn(expectedComponentName)
+
+        val unexpectedComponentName = ComponentName("unexpectedPackage", "unexpectedClass")
+        val unexpectedItemInfo1 = spy(ItemInfo())
+        unexpectedItemInfo1.user = expectedUserHandle
+        whenever(unexpectedItemInfo1.targetComponent).thenReturn(unexpectedComponentName)
+
+        val unexpectedItemInfo2 = spy(ItemInfo())
+        unexpectedItemInfo2.user = UserHandle(10)
+        whenever(unexpectedItemInfo2.targetComponent).thenReturn(expectedComponentName)
+
+        val itemInfoStream =
+            listOf(expectedItemInfo, unexpectedItemInfo1, unexpectedItemInfo2).stream()
+
+        // When
+        val predicate =
+            ItemInfoMatcher.ofComponents(hashSetOf(expectedComponentName), expectedUserHandle)
+        val actualResults = itemInfoStream.filter(predicate).toList()
+
+        // Then
+        assertThat(actualResults).containsExactly(expectedItemInfo)
+    }
+
+    @Test
+    fun `ofPackages returns Predicate for ItemInfo containing UserHandle and target package`() {
+        // Given
+        val expectedUserHandle = UserHandle(0)
+        val expectedPackage = "expectedPackage"
+        val expectedComponentName = ComponentName(expectedPackage, "expectedClass")
+        val expectedItemInfo = spy(ItemInfo())
+        expectedItemInfo.user = expectedUserHandle
+        whenever(expectedItemInfo.targetComponent).thenReturn(expectedComponentName)
+
+        val unexpectedPackage = "unexpectedPackage"
+        val unexpectedComponentName = ComponentName(unexpectedPackage, "unexpectedClass")
+        val unexpectedItemInfo1 = spy(ItemInfo())
+        unexpectedItemInfo1.user = expectedUserHandle
+        whenever(unexpectedItemInfo1.targetComponent).thenReturn(unexpectedComponentName)
+
+        val unexpectedItemInfo2 = spy(ItemInfo())
+        unexpectedItemInfo2.user = UserHandle(10)
+        whenever(unexpectedItemInfo2.targetComponent).thenReturn(expectedComponentName)
+
+        val itemInfoStream =
+            listOf(expectedItemInfo, unexpectedItemInfo1, unexpectedItemInfo2).stream()
+
+        // When
+        val predicate = ItemInfoMatcher.ofPackages(setOf(expectedPackage), expectedUserHandle)
+        val actualResults = itemInfoStream.filter(predicate).toList()
+
+        // Then
+        assertThat(actualResults).containsExactly(expectedItemInfo)
+    }
+
+    @Test
+    fun `ofShortcutKeys returns Predicate for Deep Shortcut Info containing given ShortcutKey`() {
+        // Given
+        val expectedItemInfo = spy(ItemInfo())
+        expectedItemInfo.itemType = ITEM_TYPE_DEEP_SHORTCUT
+        val expectedIntent =
+            Intent().apply {
+                putExtra("shortcut_id", "expectedShortcut")
+                `package` = "expectedPackage"
+            }
+        whenever(expectedItemInfo.intent).thenReturn(expectedIntent)
+
+        val unexpectedIntent =
+            Intent().apply {
+                putExtra("shortcut_id", "unexpectedShortcut")
+                `package` = "unexpectedPackage"
+            }
+        val unexpectedItemInfo = spy(ItemInfo())
+        unexpectedItemInfo.itemType = ITEM_TYPE_DEEP_SHORTCUT
+        whenever(unexpectedItemInfo.intent).thenReturn(unexpectedIntent)
+
+        val itemInfoStream = listOf(expectedItemInfo, unexpectedItemInfo).stream()
+        val expectedShortcutKey = ShortcutKey.fromItemInfo(expectedItemInfo)
+
+        // When
+        val predicate = ItemInfoMatcher.ofShortcutKeys(setOf(expectedShortcutKey))
+        val actualResults = itemInfoStream.filter(predicate).toList()
+
+        // Then
+        assertThat(actualResults).containsExactly(expectedItemInfo)
+    }
+
+    @Test
+    fun `forFolderMatch returns Predicate to match against children within Folder ItemInfo`() {
+        // Given
+        val expectedItemInfo = spy(FolderInfo())
+        expectedItemInfo.itemType = ITEM_TYPE_FOLDER
+        val expectedIntent =
+            Intent().apply {
+                putExtra("shortcut_id", "expectedShortcut")
+                `package` = "expectedPackage"
+            }
+        val expectedChildInfo = spy(ItemInfo())
+        expectedChildInfo.itemType = ITEM_TYPE_DEEP_SHORTCUT
+        whenever(expectedChildInfo.intent).thenReturn(expectedIntent)
+        whenever(expectedItemInfo.getContents()).thenReturn(arrayListOf(expectedChildInfo))
+
+        val unexpectedItemInfo = spy(FolderInfo())
+        unexpectedItemInfo.itemType = ITEM_TYPE_FOLDER
+
+        val itemInfoStream = listOf(expectedItemInfo, unexpectedItemInfo).stream()
+        val expectedShortcutKey = ShortcutKey.fromItemInfo(expectedChildInfo)
+
+        // When
+        val predicate = ItemInfoMatcher.forFolderMatch(ofShortcutKeys(setOf(expectedShortcutKey)))
+        val actualResults = itemInfoStream.filter(predicate).toList()
+
+        // Then
+        assertThat(actualResults).containsExactly(expectedItemInfo)
+    }
+
+    @Test
+    fun `ofItemIds returns Predicate to match ItemInfo that contains given ids`() {
+        // Given
+        val expectedItemInfo = spy(ItemInfo())
+        expectedItemInfo.id = 1
+
+        val unexpectedItemInfo = spy(ItemInfo())
+        unexpectedItemInfo.id = 2
+
+        val itemInfoStream = listOf(expectedItemInfo, unexpectedItemInfo).stream()
+
+        // When
+        val expectedIds = IntSet().apply { add(1) }
+        val predicate = ItemInfoMatcher.ofItemIds(expectedIds)
+        val actualResults = itemInfoStream.filter(predicate).toList()
+
+        // Then
+        assertThat(actualResults).containsExactly(expectedItemInfo)
+    }
+
+    @Test
+    fun `ofItems returns Predicate matching against provided ItemInfo`() {
+        // Given
+        val expectedItemInfo = spy(ItemInfo())
+        expectedItemInfo.id = 1
+
+        val unexpectedItemInfo = spy(ItemInfo())
+        unexpectedItemInfo.id = 2
+
+        val itemInfoStream = listOf(expectedItemInfo, unexpectedItemInfo).stream()
+
+        // When
+        val expectedItems = setOf(expectedItemInfo)
+        val predicate = ItemInfoMatcher.ofItems(expectedItems)
+        val actualResults = itemInfoStream.filter(predicate).toList()
+
+        // Then
+        assertThat(actualResults).containsExactly(expectedItemInfo)
+    }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/RunnableListTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/RunnableListTest.kt
index 093afc9..94bd7c9 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/RunnableListTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/util/RunnableListTest.kt
@@ -16,17 +16,20 @@
 
 package com.android.launcher3.util
 
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
+import org.junit.runner.RunWith
 import org.mockito.Mock
 import org.mockito.MockitoAnnotations
 import org.mockito.kotlin.reset
 import org.mockito.kotlin.verify
-import org.mockito.kotlin.verifyZeroInteractions
+import org.mockito.kotlin.verifyNoMoreInteractions
 
 @SmallTest
+@RunWith(AndroidJUnit4::class)
 class RunnableListTest {
 
     @Mock private lateinit var runnable1: Runnable
@@ -73,7 +76,7 @@
 
         underTest.executeAllAndDestroy()
 
-        verifyZeroInteractions(runnable1)
+        verifyNoMoreInteractions(runnable1)
     }
 
     @Test
@@ -107,7 +110,7 @@
         underTest.remove(runnable1)
         underTest.executeAllAndClear()
 
-        verifyZeroInteractions(runnable1)
+        verifyNoMoreInteractions(runnable1)
         verify(runnable2).run()
     }
 }
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/ScreenOnTrackerTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/ScreenOnTrackerTest.kt
new file mode 100644
index 0000000..430aad2
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/util/ScreenOnTrackerTest.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util
+
+import android.content.Context
+import android.content.Intent
+import android.content.Intent.ACTION_SCREEN_OFF
+import android.content.Intent.ACTION_SCREEN_ON
+import android.content.Intent.ACTION_USER_PRESENT
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.verifyNoMoreInteractions
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ScreenOnTrackerTest {
+
+    @Mock private lateinit var receiver: SimpleBroadcastReceiver
+    @Mock private lateinit var context: Context
+    @Mock private lateinit var listener: ScreenOnTracker.ScreenOnListener
+
+    private lateinit var underTest: ScreenOnTracker
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        underTest = ScreenOnTracker(context, receiver)
+    }
+
+    @Test
+    fun test_default_state() {
+        verify(receiver).register(context, ACTION_SCREEN_ON, ACTION_SCREEN_OFF, ACTION_USER_PRESENT)
+        assertThat(underTest.isScreenOn).isTrue()
+    }
+
+    @Test
+    fun close_unregister_receiver() {
+        underTest.close()
+
+        verify(receiver).unregisterReceiverSafely(context)
+    }
+
+    @Test
+    fun add_listener_then_receive_screen_on_intent_notify_listener() {
+        underTest.addListener(listener)
+
+        underTest.onReceive(Intent(ACTION_SCREEN_ON))
+
+        verify(listener).onScreenOnChanged(true)
+        assertThat(underTest.isScreenOn).isTrue()
+    }
+
+    @Test
+    fun add_listener_then_receive_screen_off_intent_notify_listener() {
+        underTest.addListener(listener)
+
+        underTest.onReceive(Intent(ACTION_SCREEN_OFF))
+
+        verify(listener).onScreenOnChanged(false)
+        assertThat(underTest.isScreenOn).isFalse()
+    }
+
+    @Test
+    fun add_listener_then_receive_user_present_intent_notify_listener() {
+        underTest.addListener(listener)
+
+        underTest.onReceive(Intent(ACTION_USER_PRESENT))
+
+        verify(listener).onUserPresent()
+        assertThat(underTest.isScreenOn).isTrue()
+    }
+
+    @Test
+    fun remove_listener_then_receive_intent_noOp() {
+        underTest.addListener(listener)
+
+        underTest.removeListener(listener)
+
+        underTest.onReceive(Intent(ACTION_USER_PRESENT))
+        verifyNoMoreInteractions(listener)
+    }
+}
diff --git a/tests/src/com/android/launcher3/util/SimpleBroadcastReceiverTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/SimpleBroadcastReceiverTest.kt
similarity index 100%
rename from tests/src/com/android/launcher3/util/SimpleBroadcastReceiverTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/util/SimpleBroadcastReceiverTest.kt
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/SystemUiControllerTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/SystemUiControllerTest.kt
new file mode 100644
index 0000000..612fcd4
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/util/SystemUiControllerTest.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util
+
+import android.view.View
+import android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+import android.view.Window
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.util.SystemUiController.FLAG_DARK_NAV
+import com.android.launcher3.util.SystemUiController.FLAG_DARK_STATUS
+import com.android.launcher3.util.SystemUiController.FLAG_LIGHT_NAV
+import com.android.launcher3.util.SystemUiController.FLAG_LIGHT_STATUS
+import com.android.launcher3.util.SystemUiController.UI_STATE_BASE_WINDOW
+import com.android.launcher3.util.SystemUiController.UI_STATE_SCRIM_VIEW
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SystemUiControllerTest {
+
+    @Mock private lateinit var window: Window
+    @Mock private lateinit var decorView: View
+
+    private lateinit var underTest: SystemUiController
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        `when`(window.decorView).thenReturn(decorView)
+        underTest = SystemUiController(window)
+    }
+
+    @Test
+    fun test_default_state() {
+        assertThat(underTest.toString()).isEqualTo("mStates=[0, 0, 0, 0, 0]")
+    }
+
+    @Test
+    fun update_state_base_window_light() {
+        underTest.updateUiState(UI_STATE_BASE_WINDOW, /* isLight= */ true)
+
+        val visibility =
+            View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
+        verify(decorView).systemUiVisibility = eq(visibility)
+        assertThat(underTest.baseSysuiVisibility).isEqualTo(visibility)
+        val flag = FLAG_LIGHT_NAV or FLAG_LIGHT_STATUS
+        assertThat(underTest.toString()).isEqualTo("mStates=[$flag, 0, 0, 0, 0]")
+    }
+
+    @Test
+    fun update_state_scrim_view_light() {
+        underTest.updateUiState(UI_STATE_SCRIM_VIEW, /* isLight= */ true)
+
+        verify(decorView).systemUiVisibility =
+            eq(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR)
+        assertThat(underTest.baseSysuiVisibility).isEqualTo(0)
+        val flag = FLAG_LIGHT_NAV or FLAG_LIGHT_STATUS
+        assertThat(underTest.toString()).isEqualTo("mStates=[0, $flag, 0, 0, 0]")
+    }
+
+    @Test
+    fun update_state_base_window_dark() {
+        underTest.updateUiState(UI_STATE_BASE_WINDOW, /* isLight= */ false)
+
+        verify(decorView, never()).systemUiVisibility = anyInt()
+        assertThat(underTest.baseSysuiVisibility).isEqualTo(0)
+        val flag = FLAG_DARK_NAV or FLAG_DARK_STATUS
+        assertThat(underTest.toString()).isEqualTo("mStates=[$flag, 0, 0, 0, 0]")
+    }
+
+    @Test
+    fun update_state_scrim_view_dark() {
+        underTest.updateUiState(UI_STATE_SCRIM_VIEW, /* isLight= */ false)
+
+        verify(decorView, never()).systemUiVisibility = anyInt()
+        assertThat(underTest.baseSysuiVisibility).isEqualTo(0)
+        val flag = FLAG_DARK_NAV or FLAG_DARK_STATUS
+        assertThat(underTest.toString()).isEqualTo("mStates=[0, $flag, 0, 0, 0]")
+    }
+
+    @Test
+    fun get_base_sysui_visibility() {
+        `when`(decorView.systemUiVisibility).thenReturn(SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
+
+        assertThat(underTest.baseSysuiVisibility).isEqualTo(SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
+    }
+
+    @Test
+    fun update_state_base_window_light_with_existing_visibility() {
+        `when`(decorView.systemUiVisibility).thenReturn(SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
+
+        underTest.updateUiState(UI_STATE_BASE_WINDOW, /* isLight= */ true)
+
+        val visibility =
+            View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR or
+                View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR or
+                SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+        assertThat(underTest.baseSysuiVisibility).isEqualTo(visibility)
+        verify(decorView).systemUiVisibility = eq(visibility)
+    }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/ViewCacheTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/ViewCacheTest.kt
index bad21c9..d82818d 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/ViewCacheTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/util/ViewCacheTest.kt
@@ -18,6 +18,7 @@
 
 import android.view.View
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.launcher3.R
 import com.android.launcher3.util.ViewCache.CacheEntry
@@ -26,6 +27,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 
+@SmallTest
 @RunWith(AndroidJUnit4::class)
 class ViewCacheTest {
 
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/ViewPoolTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/ViewPoolTest.kt
new file mode 100644
index 0000000..e535656
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/util/ViewPoolTest.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.test.annotation.UiThreadTest
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.same
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.verifyNoMoreInteractions
+
+@SmallTest
+@RunWith(LauncherMultivalentJUnit::class)
+@UiThreadTest
+class ViewPoolTest {
+
+    @Mock private lateinit var viewParent: ViewGroup
+    @Mock private lateinit var view: ReusableView
+    @Mock private lateinit var inflater: LayoutInflater
+
+    private lateinit var underTest: ViewPool<ReusableView>
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        `when`(inflater.cloneInContext(any())).thenReturn(inflater)
+        underTest = ViewPool(inflater, viewParent, LAYOUT_ID, 10, 0)
+    }
+
+    @Test
+    fun get_view_from_empty_pool_inflate_new_view() {
+        underTest.view
+
+        verify(inflater).inflate(eq(LAYOUT_ID), same(viewParent), eq(false))
+    }
+
+    @Test
+    fun recycle_view() {
+        underTest.recycle(view)
+
+        val returnedView = underTest.view
+
+        verify(view).onRecycle()
+        assertThat(returnedView).isSameInstanceAs(view)
+        verifyNoMoreInteractions(inflater)
+    }
+
+    @Test
+    fun get_view_twice_from_view_pool_with_one_view() {
+        underTest.recycle(view)
+        underTest.view
+        verifyNoMoreInteractions(inflater)
+
+        underTest.view
+
+        verify(inflater).inflate(eq(LAYOUT_ID), same(viewParent), eq(false))
+    }
+
+    companion object {
+        private const val LAYOUT_ID = 1000
+    }
+
+    private inner class ReusableView(context: Context) : View(context), ViewPool.Reusable {
+        override fun onRecycle() {
+            TODO("Not yet implemented")
+        }
+    }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/RoundedCornerEnforcementTest.kt b/tests/multivalentTests/src/com/android/launcher3/widget/RoundedCornerEnforcementTest.kt
new file mode 100644
index 0000000..db77702
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/RoundedCornerEnforcementTest.kt
@@ -0,0 +1,106 @@
+/*
+ * 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.widget
+
+import android.content.Context
+import android.content.res.Resources
+import android.graphics.Rect
+import android.view.View
+import android.view.ViewGroup
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.R
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertSame
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class RoundedCornerEnforcementTest {
+
+    @Test
+    fun `Widget view has one background`() {
+        val mockWidgetView = mock(LauncherAppWidgetHostView::class.java)
+
+        doReturn(android.R.id.background).whenever(mockWidgetView).id
+
+        assertSame(RoundedCornerEnforcement.findBackground(mockWidgetView), mockWidgetView)
+    }
+
+    @Test
+    fun `Widget opted out of rounded corner enforcement`() {
+        val mockView = mock(View::class.java)
+
+        doReturn(android.R.id.background).whenever(mockView).id
+        doReturn(true).whenever(mockView).clipToOutline
+
+        assertTrue(RoundedCornerEnforcement.hasAppWidgetOptedOut(mockView))
+    }
+
+    @Test
+    fun `Compute rect based on widget view with background`() {
+        val mockBackgroundView = mock(View::class.java)
+        val mockWidgetView = mock(ViewGroup::class.java)
+        val testRect = Rect(0, 0, 0, 0)
+
+        doReturn(WIDTH).whenever(mockBackgroundView).width
+        doReturn(HEIGHT).whenever(mockBackgroundView).height
+        doReturn(LEFT).whenever(mockBackgroundView).left
+        doReturn(TOP).whenever(mockBackgroundView).top
+        doReturn(mockWidgetView).whenever(mockBackgroundView).parent
+
+        RoundedCornerEnforcement.computeRoundedRectangle(
+            mockWidgetView,
+            mockBackgroundView,
+            testRect
+        )
+
+        assertEquals(Rect(50, 75, 250, 275), testRect)
+    }
+
+    @Test
+    fun `Compute system radius`() {
+        val mockContext = mock(Context::class.java)
+        val mockRes = mock(Resources::class.java)
+
+        doReturn(mockRes).whenever(mockContext).resources
+        doReturn(RADIUS)
+            .whenever(mockRes)
+            .getDimension(eq(android.R.dimen.system_app_widget_background_radius))
+        doReturn(LAUNCHER_RADIUS)
+            .whenever(mockRes)
+            .getDimension(eq(R.dimen.enforced_rounded_corner_max_radius))
+
+        assertEquals(RADIUS, RoundedCornerEnforcement.computeEnforcedRadius(mockContext))
+    }
+
+    companion object {
+        const val WIDTH = 200
+        const val HEIGHT = 200
+        const val LEFT = 50
+        const val TOP = 75
+
+        const val RADIUS = 8f
+        const val LAUNCHER_RADIUS = 16f
+    }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/custom/CustomAppWidgetProviderInfoTest.kt b/tests/multivalentTests/src/com/android/launcher3/widget/custom/CustomAppWidgetProviderInfoTest.kt
new file mode 100644
index 0000000..0a3035a
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/custom/CustomAppWidgetProviderInfoTest.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.widget.custom
+
+import android.content.ComponentName
+import android.content.pm.PackageManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class CustomAppWidgetProviderInfoTest {
+
+    private lateinit var underTest: CustomAppWidgetProviderInfo
+
+    @Before
+    fun setup() {
+        underTest = CustomAppWidgetProviderInfo()
+        underTest.provider = PROVIDER_NAME
+    }
+
+    @Test
+    fun info_to_string() {
+        assertEquals("WidgetProviderInfo($PROVIDER_NAME)", underTest.toString())
+    }
+
+    @Test
+    fun get_label() {
+        underTest.label = "  TEST_LABEL"
+        assertEquals(LABEL_NAME, underTest.getLabel(mock(PackageManager::class.java)))
+    }
+
+    companion object {
+        private val PROVIDER_NAME =
+            ComponentName(getInstrumentation().targetContext.packageName, "TEST_PACKAGE")
+        private const val LABEL_NAME = "TEST_LABEL"
+    }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/custom/CustomWidgetManagerTest.kt b/tests/multivalentTests/src/com/android/launcher3/widget/custom/CustomWidgetManagerTest.kt
new file mode 100644
index 0000000..4b5710d
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/custom/CustomWidgetManagerTest.kt
@@ -0,0 +1,144 @@
+/*
+ * 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.widget.custom
+
+import android.appwidget.AppWidgetManager
+import android.content.ComponentName
+import android.os.Process
+import android.platform.test.flag.junit.SetFlagsRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext
+import com.android.launcher3.util.PluginManagerWrapper
+import com.android.launcher3.util.WidgetUtils
+import com.android.launcher3.widget.LauncherAppWidgetHostView
+import com.android.launcher3.widget.LauncherAppWidgetProviderInfo
+import com.android.systemui.plugins.CustomWidgetPlugin
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.same
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CustomWidgetManagerTest {
+
+    @get:Rule val setFlagsRule = SetFlagsRule()
+
+    private val context = SandboxModelContext()
+    private lateinit var underTest: CustomWidgetManager
+
+    @Mock private lateinit var pluginManager: PluginManagerWrapper
+    @Mock private lateinit var mockAppWidgetManager: AppWidgetManager
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        context.putObject(PluginManagerWrapper.INSTANCE, pluginManager)
+        underTest = CustomWidgetManager(context, mockAppWidgetManager)
+    }
+
+    @After
+    fun tearDown() {
+        underTest.close()
+    }
+
+    @Test
+    fun plugin_manager_added_after_initialization() {
+        verify(pluginManager)
+            .addPluginListener(same(underTest), same(CustomWidgetPlugin::class.java), eq(true))
+    }
+
+    @Test
+    fun close_widget_manager_should_remove_plugin_listener() {
+        underTest.close()
+        verify(pluginManager).removePluginListener(same(underTest))
+    }
+
+    @Test
+    fun on_plugin_connected_no_provider_info() {
+        doReturn(emptyList<LauncherAppWidgetProviderInfo>())
+            .whenever(mockAppWidgetManager)
+            .getInstalledProvidersForProfile(any())
+        val mockPlugin = mock(CustomWidgetPlugin::class.java)
+        underTest.onPluginConnected(mockPlugin, context)
+        assertEquals(0, underTest.plugins.size)
+    }
+
+    @Test
+    fun on_plugin_connected_exist_provider_info() {
+        doReturn(listOf(WidgetUtils.createAppWidgetProviderInfo(TEST_COMPONENT_NAME)))
+            .whenever(mockAppWidgetManager)
+            .getInstalledProvidersForProfile(eq(Process.myUserHandle()))
+        val mockPlugin = mock(CustomWidgetPlugin::class.java)
+        underTest.onPluginConnected(mockPlugin, context)
+        assertEquals(1, underTest.plugins.size)
+    }
+
+    @Test
+    fun on_plugin_disconnected() {
+        doReturn(listOf(WidgetUtils.createAppWidgetProviderInfo(TEST_COMPONENT_NAME)))
+            .whenever(mockAppWidgetManager)
+            .getInstalledProvidersForProfile(eq(Process.myUserHandle()))
+        val mockPlugin = mock(CustomWidgetPlugin::class.java)
+        underTest.onPluginConnected(mockPlugin, context)
+        underTest.onPluginDisconnected(mockPlugin)
+        assertEquals(0, underTest.plugins.size)
+    }
+
+    @Test
+    fun on_view_created() {
+        val mockPlugin = mock(CustomWidgetPlugin::class.java)
+        val mockWidgetView = mock(LauncherAppWidgetHostView::class.java)
+        val mockProviderInfo = mock(CustomAppWidgetProviderInfo::class.java)
+        doReturn(mockProviderInfo).whenever(mockWidgetView).appWidgetInfo
+        mockProviderInfo.provider = TEST_COMPONENT_NAME
+        underTest.plugins.put(TEST_COMPONENT_NAME, mockPlugin)
+        underTest.onViewCreated(mockWidgetView)
+        verify(mockPlugin).onViewCreated(eq(mockWidgetView))
+    }
+
+    @Test
+    fun generate_stream() {
+        assertTrue(underTest.stream().toList().isEmpty())
+        doReturn(listOf(WidgetUtils.createAppWidgetProviderInfo(TEST_COMPONENT_NAME)))
+            .whenever(mockAppWidgetManager)
+            .getInstalledProvidersForProfile(eq(Process.myUserHandle()))
+        val mockPlugin = mock(CustomWidgetPlugin::class.java)
+        underTest.onPluginConnected(mockPlugin, context)
+        assertEquals(1, underTest.stream().toList().size)
+    }
+
+    companion object {
+        private const val TEST_CLASS = "TEST_CLASS"
+        private val TEST_COMPONENT_NAME =
+            ComponentName(getInstrumentation().targetContext.packageName, TEST_CLASS)
+    }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/model/WidgetsListBaseEntriesBuilderTest.kt b/tests/multivalentTests/src/com/android/launcher3/widget/model/WidgetsListBaseEntriesBuilderTest.kt
new file mode 100644
index 0000000..5df7caa
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/model/WidgetsListBaseEntriesBuilderTest.kt
@@ -0,0 +1,178 @@
+/*
+ * 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.widget.model
+
+import android.content.ComponentName
+import android.content.Context
+import android.os.UserHandle
+import android.platform.test.rule.AllowedDevices
+import android.platform.test.rule.DeviceProduct
+import android.platform.test.rule.LimitDevicesRule
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.launcher3.InvariantDeviceProfile
+import com.android.launcher3.LauncherAppState
+import com.android.launcher3.icons.ComponentWithLabel
+import com.android.launcher3.icons.IconCache
+import com.android.launcher3.model.WidgetItem
+import com.android.launcher3.model.data.PackageItemInfo
+import com.android.launcher3.util.ActivityContextWrapper
+import com.android.launcher3.util.WidgetUtils
+import com.android.launcher3.widget.LauncherAppWidgetProviderInfo
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.invocation.InvocationOnMock
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doAnswer
+
+@RunWith(AndroidJUnit4::class)
+@AllowedDevices(allowed = [DeviceProduct.ROBOLECTRIC])
+class WidgetsListBaseEntriesBuilderTest {
+    @Rule @JvmField val limitDevicesRule = LimitDevicesRule()
+    @Rule @JvmField val mockitoRule: MockitoRule = MockitoJUnit.rule()
+
+    @Mock private lateinit var iconCache: IconCache
+
+    private lateinit var userHandle: UserHandle
+    private lateinit var context: Context
+    private lateinit var testInvariantProfile: InvariantDeviceProfile
+    private lateinit var allWidgets: Map<PackageItemInfo, List<WidgetItem>>
+    private lateinit var underTest: WidgetsListBaseEntriesBuilder
+
+    @Before
+    fun setUp() {
+        userHandle = UserHandle.CURRENT
+        context = ActivityContextWrapper(ApplicationProvider.getApplicationContext())
+        testInvariantProfile = LauncherAppState.getIDP(context)
+
+        doAnswer { invocation: InvocationOnMock ->
+                val componentWithLabel = invocation.getArgument<Any>(0) as ComponentWithLabel
+                componentWithLabel.getComponent().shortClassName
+            }
+            .`when`(iconCache)
+            .getTitleNoCache(any<ComponentWithLabel>())
+        underTest = WidgetsListBaseEntriesBuilder(context)
+
+        allWidgets =
+            mapOf(
+                // app 1
+                packageItemInfoWithTitle(APP_1_PACKAGE_NAME, APP_1_PACKAGE_TITLE) to
+                    listOf(
+                        createWidgetItem(APP_1_PACKAGE_NAME, APP_1_PROVIDER_1_CLASS_NAME),
+                        createWidgetItem(APP_1_PACKAGE_NAME, APP_1_PROVIDER_2_CLASS_NAME)
+                    ),
+                // app 2
+                packageItemInfoWithTitle(APP_2_PACKAGE_NAME, APP_2_PACKAGE_TITLE) to
+                    listOf(createWidgetItem(APP_2_PACKAGE_NAME, APP_2_PROVIDER_1_CLASS_NAME)),
+                // app 3
+                packageItemInfoWithTitle(APP_3_PACKAGE_NAME, APP_3_PACKAGE_TITLE) to
+                    listOf(createWidgetItem(APP_3_PACKAGE_NAME, APP_3_PROVIDER_1_CLASS_NAME))
+            )
+    }
+
+    @Test
+    fun widgetsListBaseEntriesBuilder_addsHeaderAndContentEntries_withCorrectSectionName() {
+        val expectedWidgetsCountBySection =
+            listOf(
+                APP_1_EXPECTED_SECTION_NAME to 2,
+                APP_2_EXPECTED_SECTION_NAME to 1,
+                APP_3_EXPECTED_SECTION_NAME to 1
+            )
+
+        val entries = underTest.build(allWidgets)
+
+        assertThat(entries).hasSize(6)
+        val headerEntrySectionAndWidgetSizes =
+            entries.filterIsInstance<WidgetsListHeaderEntry>().map {
+                it.mTitleSectionName to it.mWidgets.size
+            }
+        val contentEntrySectionAndWidgetSizes =
+            entries.filterIsInstance<WidgetsListContentEntry>().map {
+                it.mTitleSectionName to it.mWidgets.size
+            }
+        assertThat(headerEntrySectionAndWidgetSizes)
+            .containsExactlyElementsIn(expectedWidgetsCountBySection)
+        assertThat(contentEntrySectionAndWidgetSizes)
+            .containsExactlyElementsIn(expectedWidgetsCountBySection)
+    }
+
+    @Test
+    fun widgetsListBaseEntriesBuilder_withFilter_addsFilteredHeaderAndContentEntries() {
+        val allowList = listOf(APP_1_PROVIDER_1_CLASS_NAME, APP_3_PROVIDER_1_CLASS_NAME)
+        val expectedWidgetsCountBySection =
+            listOf(
+                APP_1_EXPECTED_SECTION_NAME to 1, // one widget filtered out
+                APP_3_EXPECTED_SECTION_NAME to 1
+            )
+
+        val entries =
+            underTest.build(allWidgets) { w -> allowList.contains(w.componentName.shortClassName) }
+
+        assertThat(entries).hasSize(4) // app 2 filtered out
+        val headerEntrySectionAndWidgetSizes =
+            entries.filterIsInstance<WidgetsListHeaderEntry>().map {
+                it.mTitleSectionName to it.mWidgets.size
+            }
+        val contentEntrySectionAndWidgetSizes =
+            entries.filterIsInstance<WidgetsListContentEntry>().map {
+                it.mTitleSectionName to it.mWidgets.size
+            }
+        assertThat(headerEntrySectionAndWidgetSizes)
+            .containsExactlyElementsIn(expectedWidgetsCountBySection)
+        assertThat(contentEntrySectionAndWidgetSizes)
+            .containsExactlyElementsIn(expectedWidgetsCountBySection)
+    }
+
+    private fun packageItemInfoWithTitle(packageName: String, title: String): PackageItemInfo {
+        val packageItemInfo = PackageItemInfo(packageName, userHandle)
+        packageItemInfo.title = title
+        return packageItemInfo
+    }
+
+    private fun createWidgetItem(packageName: String, widgetProviderName: String): WidgetItem {
+        val providerInfo =
+            WidgetUtils.createAppWidgetProviderInfo(
+                ComponentName.createRelative(packageName, widgetProviderName)
+            )
+        val widgetInfo = LauncherAppWidgetProviderInfo.fromProviderInfo(context, providerInfo)
+        return WidgetItem(widgetInfo, testInvariantProfile, iconCache, context)
+    }
+
+    companion object {
+        const val APP_1_PACKAGE_NAME = "com.example.app1"
+        const val APP_1_PACKAGE_TITLE = "App1"
+        const val APP_1_EXPECTED_SECTION_NAME = "A" // for fast popup
+        const val APP_1_PROVIDER_1_CLASS_NAME = "app1Provider1"
+        const val APP_1_PROVIDER_2_CLASS_NAME = "app1Provider2"
+
+        const val APP_2_PACKAGE_NAME = "com.example.app2"
+        const val APP_2_PACKAGE_TITLE = "SomeApp2"
+        const val APP_2_EXPECTED_SECTION_NAME = "S" // for fast popup
+        const val APP_2_PROVIDER_1_CLASS_NAME = "app2Provider1"
+
+        const val APP_3_PACKAGE_NAME = "com.example.app3"
+        const val APP_3_PACKAGE_TITLE = "OtherApp3"
+        const val APP_3_EXPECTED_SECTION_NAME = "O" // for fast popup
+        const val APP_3_PROVIDER_1_CLASS_NAME = "app3Provider1"
+    }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithmTest.java b/tests/multivalentTests/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithmTest.java
index 0370a6b..24d66a3 100644
--- a/tests/multivalentTests/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithmTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithmTest.java
@@ -45,12 +45,12 @@
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.model.data.PackageItemInfo;
-import com.android.launcher3.popup.PopupDataProvider;
 import com.android.launcher3.search.SearchCallback;
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.widget.model.WidgetsListBaseEntry;
 import com.android.launcher3.widget.model.WidgetsListContentEntry;
 import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
+import com.android.launcher3.widget.picker.search.WidgetsSearchBar.WidgetsSearchDataProvider;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -79,7 +79,7 @@
 
     private SimpleWidgetsSearchAlgorithm mSimpleWidgetsSearchAlgorithm;
     @Mock
-    private PopupDataProvider mDataProvider;
+    private WidgetsSearchDataProvider mDataProvider;
     @Mock
     private SearchCallback<WidgetsListBaseEntry> mSearchCallback;
 
@@ -106,7 +106,7 @@
 
         mSimpleWidgetsSearchAlgorithm = MAIN_EXECUTOR.submit(
                 () -> new SimpleWidgetsSearchAlgorithm(mDataProvider)).get();
-        doReturn(Collections.EMPTY_LIST).when(mDataProvider).getAllWidgets();
+        doReturn(Collections.EMPTY_LIST).when(mDataProvider).getWidgets();
     }
 
     @Test
@@ -114,7 +114,7 @@
         doReturn(List.of(mCalendarHeaderEntry, mCalendarContentEntry, mCameraHeaderEntry,
                 mCameraContentEntry, mClockHeaderEntry, mClockContentEntry))
                 .when(mDataProvider)
-                .getAllWidgets();
+                .getWidgets();
 
         assertEquals(List.of(
                 WidgetsListHeaderEntry.createForSearch(
@@ -135,7 +135,7 @@
         doReturn(List.of(mCalendarHeaderEntry, mCalendarContentEntry, mCameraHeaderEntry,
                 mCameraContentEntry))
                 .when(mDataProvider)
-                .getAllWidgets();
+                .getWidgets();
 
         assertEquals(List.of(
                 WidgetsListHeaderEntry.createForSearch(
@@ -162,7 +162,7 @@
         doReturn(List.of(mCalendarHeaderEntry, mCalendarContentEntry, mCameraHeaderEntry,
                 mCameraContentEntry, mClockHeaderEntry, mClockContentEntry))
                 .when(mDataProvider)
-                .getAllWidgets();
+                .getWidgets();
         mSimpleWidgetsSearchAlgorithm.doSearch("Ca", mSearchCallback);
         getInstrumentation().waitForIdleSync();
         verify(mSearchCallback).onSearchResult(
diff --git a/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewTest.java b/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewTest.java
index eac7f63..de48432 100644
--- a/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewTest.java
+++ b/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewTest.java
@@ -116,7 +116,8 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mContext = new ActivityContextWrapper(getApplicationContext());
+        mContext = new ActivityContextWrapper(getApplicationContext(),
+                R.style.DynamicColorsBaseLauncherTheme);
         when(mAllApps.getContext()).thenReturn(mContext);
         when(mUserCache.getUserInfo(PRIVATE_HANDLE)).thenReturn(PRIVATE_ICON_INFO);
         when(mUserCache.getUserProfiles())
diff --git a/tests/src/com/android/launcher3/folder/FolderPagedViewTest.kt b/tests/src/com/android/launcher3/folder/FolderPagedViewTest.kt
new file mode 100644
index 0000000..e8681dc
--- /dev/null
+++ b/tests/src/com/android/launcher3/folder/FolderPagedViewTest.kt
@@ -0,0 +1,172 @@
+/*
+ * 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.folder
+
+import android.graphics.Point
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+data class TestCase(val maxCountX: Int, val maxCountY: Int, val totalItems: Int)
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FolderPagedViewTest {
+
+    companion object {
+        private fun makeFolderGridOrganizer(testCase: TestCase): FolderGridOrganizer {
+            val folderGridOrganizer = FolderGridOrganizer(testCase.maxCountX, testCase.maxCountY)
+            folderGridOrganizer.setContentSize(testCase.totalItems)
+            return folderGridOrganizer
+        }
+    }
+
+    @Test
+    fun setContentSize() {
+        assertCountXandY(
+            TestCase(maxCountX = 4, maxCountY = 3, totalItems = 22),
+            expectedCountX = 4,
+            expectedCountY = 3
+        )
+        assertCountXandY(
+            TestCase(maxCountX = 4, maxCountY = 3, totalItems = 8),
+            expectedCountX = 3,
+            expectedCountY = 3
+        )
+        assertCountXandY(
+            TestCase(maxCountX = 4, maxCountY = 3, totalItems = 3),
+            expectedCountX = 2,
+            expectedCountY = 2
+        )
+    }
+
+    private fun assertCountXandY(testCase: TestCase, expectedCountX: Int, expectedCountY: Int) {
+        val folderGridOrganizer = makeFolderGridOrganizer(testCase)
+        assert(folderGridOrganizer.countX == expectedCountX) {
+            "Error on expected countX $expectedCountX got ${folderGridOrganizer.countX} using test case $testCase"
+        }
+        assert(folderGridOrganizer.countY == expectedCountY) {
+            "Error on expected countY $expectedCountY got ${folderGridOrganizer.countY} using test case $testCase"
+        }
+    }
+
+    @Test
+    fun getPosForRank() {
+        assertFolderRank(
+            TestCase(maxCountX = 4, maxCountY = 3, totalItems = 22),
+            expectedPos = Point(0, 0),
+            rank = 0
+        )
+        assertFolderRank(
+            TestCase(maxCountX = 4, maxCountY = 3, totalItems = 22),
+            expectedPos = Point(1, 0),
+            rank = 1
+        )
+        assertFolderRank(
+            TestCase(maxCountX = 4, maxCountY = 3, totalItems = 22),
+            expectedPos = Point(3, 0),
+            rank = 3
+        )
+        assertFolderRank(
+            TestCase(maxCountX = 4, maxCountY = 3, totalItems = 22),
+            expectedPos = Point(2, 1),
+            rank = 6
+        )
+        val testCase = TestCase(maxCountX = 4, maxCountY = 3, totalItems = 22)
+        // Rank 16 and 38 should yield the same point since 38 % 12 == 2
+        val folderGridOrganizer = makeFolderGridOrganizer(testCase)
+        assertFolderRank(testCase, expectedPos = folderGridOrganizer.getPosForRank(2), rank = 38)
+    }
+
+    private fun assertFolderRank(testCase: TestCase, expectedPos: Point, rank: Int) {
+        val folderGridOrganizer = makeFolderGridOrganizer(testCase)
+        val pos = folderGridOrganizer.getPosForRank(rank)
+        assert(pos == expectedPos) {
+            "Expected pos = $expectedPos  doesn't match pos = $pos for the given rank $rank and the give test case $testCase"
+        }
+    }
+
+    @Test
+    fun isItemInPreview() {
+        val folderGridOrganizer = FolderGridOrganizer(5, 8)
+        folderGridOrganizer.setContentSize(ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW - 1)
+        // Very few items
+        for (i in 0..3) {
+            assertItemsInPreview(
+                TestCase(
+                    maxCountX = 5,
+                    maxCountY = 8,
+                    totalItems = ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW - 1
+                ),
+                expectedIsInPreview = true,
+                page = 0,
+                rank = i
+            )
+        }
+        for (i in 4..40) {
+            assertItemsInPreview(
+                TestCase(
+                    maxCountX = 5,
+                    maxCountY = 8,
+                    totalItems = ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW - 1
+                ),
+                expectedIsInPreview = false,
+                page = 0,
+                rank = i
+            )
+        }
+        // Full of items
+        assertItemsInPreview(
+            TestCase(maxCountX = 5, maxCountY = 8, totalItems = 40),
+            expectedIsInPreview = false,
+            page = 0,
+            rank = 2
+        )
+        assertItemsInPreview(
+            TestCase(maxCountX = 5, maxCountY = 8, totalItems = 40),
+            expectedIsInPreview = false,
+            page = 0,
+            rank = 2
+        )
+        assertItemsInPreview(
+            TestCase(maxCountX = 5, maxCountY = 8, totalItems = 40),
+            expectedIsInPreview = true,
+            page = 0,
+            rank = 5
+        )
+        assertItemsInPreview(
+            TestCase(maxCountX = 5, maxCountY = 8, totalItems = 40),
+            expectedIsInPreview = true,
+            page = 0,
+            rank = 6
+        )
+    }
+
+    private fun assertItemsInPreview(
+        testCase: TestCase,
+        expectedIsInPreview: Boolean,
+        page: Int,
+        rank: Int
+    ) {
+        val folderGridOrganizer = makeFolderGridOrganizer(testCase)
+        val isInPreview = folderGridOrganizer.isItemInPreview(page, rank)
+        assert(isInPreview == expectedIsInPreview) {
+            "Item preview state should be $expectedIsInPreview but got $isInPreview, for page $page and rank $rank, for test case $testCase"
+        }
+    }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 25eae44..ad37f7b 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -98,6 +98,7 @@
 import java.util.function.BooleanSupplier;
 import java.util.function.Function;
 import java.util.function.Supplier;
+import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 
@@ -2398,6 +2399,16 @@
         disableSensorRotation();
         final Integer initialPid = getPid();
         final LogEventChecker eventChecker = new LogEventChecker(this);
+        eventChecker.setLogExclusionRule(event -> {
+            Matcher matcher = Pattern.compile("KeyEvent.*flags=0x([0-9a-fA-F]+)").matcher(event);
+            if (matcher.find()) {
+                int keyEventFlags = Integer.parseInt(matcher.group(1), 16);
+                // ignore KeyEvents with FLAG_CANCELED
+                return (keyEventFlags & KeyEvent.FLAG_CANCELED) != 0;
+            }
+            return false;
+        });
+
         if (eventChecker.start()) mEventChecker = eventChecker;
 
         return () -> {
diff --git a/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java b/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java
index 055a357..3a49160 100644
--- a/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java
+++ b/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java
@@ -35,6 +35,8 @@
     // Map from an event sequence name to an ordered list of expected events in that sequence.
     private final ListMap<Pattern> mExpectedEvents = new ListMap<>();
 
+    private LogExclusionRule mLogExclusionRule = null;
+
     LogEventChecker(LauncherInstrumentation launcher) {
         mLauncher = launcher;
     }
@@ -48,6 +50,10 @@
         mExpectedEvents.add(sequence, pattern);
     }
 
+    void setLogExclusionRule(LogExclusionRule logExclusionRule) {
+        mLogExclusionRule = logExclusionRule;
+    }
+
     // Waits for the expected number of events and returns them.
     private ListMap<String> finishSync(long waitForExpectedCountMs) {
         final long startTime = SystemClock.uptimeMillis();
@@ -74,7 +80,9 @@
         final ListMap<String> eventSequences = new ListMap<>();
         for (String rawEvent : rawEvents) {
             final String[] split = rawEvent.split("/");
-            eventSequences.add(split[0], split[1]);
+            if (mLogExclusionRule == null || !mLogExclusionRule.shouldExclude(split[1])) {
+                eventSequences.add(split[0], split[1]);
+            }
         }
         return eventSequences;
     }
@@ -175,4 +183,8 @@
             return list;
         }
     }
+
+    interface LogExclusionRule {
+        boolean shouldExclude(String event);
+    }
 }