Merge "Revert "Adding logs to debug test failures"" into main
diff --git a/quickstep/res/values-af/strings.xml b/quickstep/res/values-af/strings.xml
index c71308f..c922238 100644
--- a/quickstep/res/values-af/strings.xml
+++ b/quickstep/res/values-af/strings.xml
@@ -23,8 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Vormvry"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Rekenaar"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Skuif na eksterne skerm"</string>
-    <!-- no translation found for recent_task_option_close (942942499021777264) -->
-    <skip />
+    <string name="recent_task_option_close" msgid="942942499021777264">"Maak toe"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Werkskerm"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Geen onlangse items nie"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Programgebruikinstellings"</string>
diff --git a/quickstep/res/values-am/strings.xml b/quickstep/res/values-am/strings.xml
index a7b9957..97b0ac7 100644
--- a/quickstep/res/values-am/strings.xml
+++ b/quickstep/res/values-am/strings.xml
@@ -23,8 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"ነፃ ቅጽ"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"ዴስክቶፕ"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"ወደ ውጫዊ ማሳያ አንቀሳቅስ"</string>
-    <!-- no translation found for recent_task_option_close (942942499021777264) -->
-    <skip />
+    <string name="recent_task_option_close" msgid="942942499021777264">"ዝጋ"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"ዴስክቶፕ"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"ምንም የቅርብ ጊዜ ንጥሎች የሉም"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"የመተግበሪያ አጠቃቀም ቅንብሮች"</string>
diff --git a/quickstep/res/values-az/strings.xml b/quickstep/res/values-az/strings.xml
index ab57177..dab4070 100644
--- a/quickstep/res/values-az/strings.xml
+++ b/quickstep/res/values-az/strings.xml
@@ -23,8 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Sərbəst rejim"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Masaüstü"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Xarici displeyə köçürün"</string>
-    <!-- no translation found for recent_task_option_close (942942499021777264) -->
-    <skip />
+    <string name="recent_task_option_close" msgid="942942499021777264">"Bağlayın"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Masaüstü"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Son elementlər yoxdur"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Tətbiq istifadə ayarları"</string>
diff --git a/quickstep/res/values-be/strings.xml b/quickstep/res/values-be/strings.xml
index 08d855c..5ea5761 100644
--- a/quickstep/res/values-be/strings.xml
+++ b/quickstep/res/values-be/strings.xml
@@ -23,8 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Адвольная форма"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Працоўны стол"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Перамясціць на знешні дысплэй"</string>
-    <!-- no translation found for recent_task_option_close (942942499021777264) -->
-    <skip />
+    <string name="recent_task_option_close" msgid="942942499021777264">"Закрыць"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Працоўны стол"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Няма новых элементаў"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Налады выкарыстання праграмы"</string>
diff --git a/quickstep/res/values-bg/strings.xml b/quickstep/res/values-bg/strings.xml
index f67e8e4..5c01d29 100644
--- a/quickstep/res/values-bg/strings.xml
+++ b/quickstep/res/values-bg/strings.xml
@@ -23,8 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Свободна форма"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"За компютър"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Преместване към външния екран"</string>
-    <!-- no translation found for recent_task_option_close (942942499021777264) -->
-    <skip />
+    <string name="recent_task_option_close" msgid="942942499021777264">"Затваряне"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Настолен компютър"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Няма скорошни елементи"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Настройки за използването на приложенията"</string>
diff --git a/quickstep/res/values-bn/strings.xml b/quickstep/res/values-bn/strings.xml
index 8ff5d76..01954cf 100644
--- a/quickstep/res/values-bn/strings.xml
+++ b/quickstep/res/values-bn/strings.xml
@@ -23,8 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"ফ্রি-ফর্ম"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"ডেস্কটপ"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"এক্সটার্নাল ডিসপ্লেতে সরিয়ে নিয়ে যান"</string>
-    <!-- no translation found for recent_task_option_close (942942499021777264) -->
-    <skip />
+    <string name="recent_task_option_close" msgid="942942499021777264">"বন্ধ করুন"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"ডেস্কটপ"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"কোনও সাম্প্রতিক আইটেম নেই"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"অ্যাপ ব্যবহারের সেটিংস"</string>
diff --git a/quickstep/res/values-bs/strings.xml b/quickstep/res/values-bs/strings.xml
index 9553e28..18c178b 100644
--- a/quickstep/res/values-bs/strings.xml
+++ b/quickstep/res/values-bs/strings.xml
@@ -23,7 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Slobodan oblik"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Radna površina"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Premještanje na vanjski ekran"</string>
-    <string name="recent_task_option_close" msgid="942942499021777264">"Zatvori"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"Zatvaranje"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Radna površina"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Nema nedavnih stavki"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Postavke korištenja aplikacije"</string>
diff --git a/quickstep/res/values-ca/strings.xml b/quickstep/res/values-ca/strings.xml
index 44ef6c3..23cd439 100644
--- a/quickstep/res/values-ca/strings.xml
+++ b/quickstep/res/values-ca/strings.xml
@@ -23,8 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Format lliure"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Escriptori"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Mou a la pantalla externa"</string>
-    <!-- no translation found for recent_task_option_close (942942499021777264) -->
-    <skip />
+    <string name="recent_task_option_close" msgid="942942499021777264">"Tanca"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Escriptori"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"No hi ha cap element recent"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Configuració d\'ús d\'aplicacions"</string>
diff --git a/quickstep/res/values-da/strings.xml b/quickstep/res/values-da/strings.xml
index 224ac11..4bae930 100644
--- a/quickstep/res/values-da/strings.xml
+++ b/quickstep/res/values-da/strings.xml
@@ -23,8 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Frit format"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Computertilstand"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Flyt til ekstern skærm"</string>
-    <!-- no translation found for recent_task_option_close (942942499021777264) -->
-    <skip />
+    <string name="recent_task_option_close" msgid="942942499021777264">"Luk"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Computertilstand"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Ingen nye elementer"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Indstillinger for appforbrug"</string>
diff --git a/quickstep/res/values-de/strings.xml b/quickstep/res/values-de/strings.xml
index 40c1cce..e937410 100644
--- a/quickstep/res/values-de/strings.xml
+++ b/quickstep/res/values-de/strings.xml
@@ -23,8 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform-Modus"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktopmodus"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Auf externes Display verschieben"</string>
-    <!-- no translation found for recent_task_option_close (942942499021777264) -->
-    <skip />
+    <string name="recent_task_option_close" msgid="942942499021777264">"Schließen"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Desktopmodus"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Keine kürzlich verwendeten Elemente"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Einstellungen zur App-Nutzung"</string>
diff --git a/quickstep/res/values-el/strings.xml b/quickstep/res/values-el/strings.xml
index 046e047..438dcf8 100644
--- a/quickstep/res/values-el/strings.xml
+++ b/quickstep/res/values-el/strings.xml
@@ -23,8 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Ελεύθερη μορφή"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Υπολογιστής"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Μετακίνηση σε εξωτερική οθόνη"</string>
-    <!-- no translation found for recent_task_option_close (942942499021777264) -->
-    <skip />
+    <string name="recent_task_option_close" msgid="942942499021777264">"Κλείσιμο"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Υπολογιστής"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Δεν υπάρχουν πρόσφατα στοιχεία"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Ρυθμίσεις χρήσης εφαρμογής"</string>
diff --git a/quickstep/res/values-en-rAU/strings.xml b/quickstep/res/values-en-rAU/strings.xml
index 0c8384c..954db96 100644
--- a/quickstep/res/values-en-rAU/strings.xml
+++ b/quickstep/res/values-en-rAU/strings.xml
@@ -23,8 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Move to external display"</string>
-    <!-- no translation found for recent_task_option_close (942942499021777264) -->
-    <skip />
+    <string name="recent_task_option_close" msgid="942942499021777264">"Close"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Desktop"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"No recent items"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"App usage settings"</string>
diff --git a/quickstep/res/values-en-rGB/strings.xml b/quickstep/res/values-en-rGB/strings.xml
index 0c8384c..954db96 100644
--- a/quickstep/res/values-en-rGB/strings.xml
+++ b/quickstep/res/values-en-rGB/strings.xml
@@ -23,8 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Move to external display"</string>
-    <!-- no translation found for recent_task_option_close (942942499021777264) -->
-    <skip />
+    <string name="recent_task_option_close" msgid="942942499021777264">"Close"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Desktop"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"No recent items"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"App usage settings"</string>
diff --git a/quickstep/res/values-en-rIN/strings.xml b/quickstep/res/values-en-rIN/strings.xml
index 0c8384c..954db96 100644
--- a/quickstep/res/values-en-rIN/strings.xml
+++ b/quickstep/res/values-en-rIN/strings.xml
@@ -23,8 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Move to external display"</string>
-    <!-- no translation found for recent_task_option_close (942942499021777264) -->
-    <skip />
+    <string name="recent_task_option_close" msgid="942942499021777264">"Close"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Desktop"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"No recent items"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"App usage settings"</string>
diff --git a/quickstep/res/values-es/strings.xml b/quickstep/res/values-es/strings.xml
index 6c412f7..7f7b433 100644
--- a/quickstep/res/values-es/strings.xml
+++ b/quickstep/res/values-es/strings.xml
@@ -23,8 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Formato libre"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Escritorio"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Mover a pantalla externa"</string>
-    <!-- no translation found for recent_task_option_close (942942499021777264) -->
-    <skip />
+    <string name="recent_task_option_close" msgid="942942499021777264">"Cerrar"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Ordenador"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"No hay nada reciente"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Ajustes de uso de la aplicación"</string>
diff --git a/quickstep/res/values-eu/strings.xml b/quickstep/res/values-eu/strings.xml
index 892089d..0a912d8 100644
--- a/quickstep/res/values-eu/strings.xml
+++ b/quickstep/res/values-eu/strings.xml
@@ -23,8 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Modu librea"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Ordenagailua"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Eraman kanpoko pantailara"</string>
-    <!-- no translation found for recent_task_option_close (942942499021777264) -->
-    <skip />
+    <string name="recent_task_option_close" msgid="942942499021777264">"Itxi"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Mahaigaina"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Ez dago azkenaldi honetako ezer"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Aplikazioen erabileraren ezarpenak"</string>
diff --git a/quickstep/res/values-fi/strings.xml b/quickstep/res/values-fi/strings.xml
index 0e360d0..adeaa34 100644
--- a/quickstep/res/values-fi/strings.xml
+++ b/quickstep/res/values-fi/strings.xml
@@ -23,8 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Vapaamuotoinen"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Tietokone"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Siirrä ulkoiselle näytölle"</string>
-    <!-- no translation found for recent_task_option_close (942942499021777264) -->
-    <skip />
+    <string name="recent_task_option_close" msgid="942942499021777264">"Sulje"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Tietokone"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Ei viimeaikaisia kohteita"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Sovelluksen käyttöasetukset"</string>
diff --git a/quickstep/res/values-gl/strings.xml b/quickstep/res/values-gl/strings.xml
index f4260f3..edd55d5 100644
--- a/quickstep/res/values-gl/strings.xml
+++ b/quickstep/res/values-gl/strings.xml
@@ -23,8 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Forma libre"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Escritorio"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Mover á pantalla externa"</string>
-    <!-- no translation found for recent_task_option_close (942942499021777264) -->
-    <skip />
+    <string name="recent_task_option_close" msgid="942942499021777264">"Pechar"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Ordenador"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Non hai elementos recentes"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Configuración do uso de aplicacións"</string>
diff --git a/quickstep/res/values-gu/strings.xml b/quickstep/res/values-gu/strings.xml
index 2797f82..7145c6c9 100644
--- a/quickstep/res/values-gu/strings.xml
+++ b/quickstep/res/values-gu/strings.xml
@@ -23,8 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"ફ્રિફોર્મ"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"ડેસ્કટૉપ"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"બાહ્ય ડિસ્પ્લે પર ખસેડો"</string>
-    <!-- no translation found for recent_task_option_close (942942499021777264) -->
-    <skip />
+    <string name="recent_task_option_close" msgid="942942499021777264">"બંધ કરો"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"ડેસ્કટૉપ"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"તાજેતરની કોઈ આઇટમ નથી"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ઍપ વપરાશનું સેટિંગ"</string>
diff --git a/quickstep/res/values-hi/strings.xml b/quickstep/res/values-hi/strings.xml
index 5ed6ef5..99b7b71 100644
--- a/quickstep/res/values-hi/strings.xml
+++ b/quickstep/res/values-hi/strings.xml
@@ -23,8 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"फ़्रीफ़ॉर्म"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"डेस्कटॉप"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"बाहरी डिसप्ले पर जाएं"</string>
-    <!-- no translation found for recent_task_option_close (942942499021777264) -->
-    <skip />
+    <string name="recent_task_option_close" msgid="942942499021777264">"बंद करें"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"डेस्कटॉप"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"हाल ही का कोई आइटम नहीं है"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ऐप्लिकेशन इस्तेमाल की सेटिंग"</string>
diff --git a/quickstep/res/values-hu/strings.xml b/quickstep/res/values-hu/strings.xml
index 4f15c7d..f825032 100644
--- a/quickstep/res/values-hu/strings.xml
+++ b/quickstep/res/values-hu/strings.xml
@@ -23,8 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Szabad forma"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Asztali"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Áthelyezés külső kijelzőre"</string>
-    <!-- no translation found for recent_task_option_close (942942499021777264) -->
-    <skip />
+    <string name="recent_task_option_close" msgid="942942499021777264">"Bezárás"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Asztali"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Nincsenek mostanában használt elemek"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Alkalmazáshasználati beállítások"</string>
diff --git a/quickstep/res/values-hy/strings.xml b/quickstep/res/values-hy/strings.xml
index edaa5bd..6a7653f 100644
--- a/quickstep/res/values-hy/strings.xml
+++ b/quickstep/res/values-hy/strings.xml
@@ -23,8 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Կամայական ձև"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Համակարգիչ"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Տեղափոխել արտաքին էկրան"</string>
-    <!-- no translation found for recent_task_option_close (942942499021777264) -->
-    <skip />
+    <string name="recent_task_option_close" msgid="942942499021777264">"Փակել"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Համակարգիչ"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Այստեղ դեռ ոչինչ չկա"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Հավելվածի օգտագործման կարգավորումներ"</string>
diff --git a/quickstep/res/values-in/strings.xml b/quickstep/res/values-in/strings.xml
index 4b2ffd7..5b5796c 100644
--- a/quickstep/res/values-in/strings.xml
+++ b/quickstep/res/values-in/strings.xml
@@ -23,8 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Format bebas"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Pindahkan ke layar eksternal"</string>
-    <!-- no translation found for recent_task_option_close (942942499021777264) -->
-    <skip />
+    <string name="recent_task_option_close" msgid="942942499021777264">"Tutup"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Desktop"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Tidak ada item yang baru dibuka"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Setelan penggunaan aplikasi"</string>
diff --git a/quickstep/res/values-is/strings.xml b/quickstep/res/values-is/strings.xml
index 3619a2d..12541f7 100644
--- a/quickstep/res/values-is/strings.xml
+++ b/quickstep/res/values-is/strings.xml
@@ -23,8 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Frjálst snið"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Tölva"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Færa í annað tæki"</string>
-    <!-- no translation found for recent_task_option_close (942942499021777264) -->
-    <skip />
+    <string name="recent_task_option_close" msgid="942942499021777264">"Loka"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Tölva"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Engin nýleg atriði"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Notkunarstillingar forrits"</string>
diff --git a/quickstep/res/values-it/strings.xml b/quickstep/res/values-it/strings.xml
index 17aefa4..731184b 100644
--- a/quickstep/res/values-it/strings.xml
+++ b/quickstep/res/values-it/strings.xml
@@ -23,8 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Forma libera"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Sposta sul display esterno"</string>
-    <!-- no translation found for recent_task_option_close (942942499021777264) -->
-    <skip />
+    <string name="recent_task_option_close" msgid="942942499021777264">"Chiudi"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Desktop"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Nessun elemento recente"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Impostazioni di utilizzo delle app"</string>
diff --git a/quickstep/res/values-iw/strings.xml b/quickstep/res/values-iw/strings.xml
index 1cba30f..0fff001 100644
--- a/quickstep/res/values-iw/strings.xml
+++ b/quickstep/res/values-iw/strings.xml
@@ -23,8 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"מצב חופשי"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"במחשב"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"העברה למסך חיצוני"</string>
-    <!-- no translation found for recent_task_option_close (942942499021777264) -->
-    <skip />
+    <string name="recent_task_option_close" msgid="942942499021777264">"סגירה"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"מחשב"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"אין פריטים אחרונים"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"הגדרות שימוש באפליקציה"</string>
diff --git a/quickstep/res/values-kk/strings.xml b/quickstep/res/values-kk/strings.xml
index b95e708..76a6199 100644
--- a/quickstep/res/values-kk/strings.xml
+++ b/quickstep/res/values-kk/strings.xml
@@ -23,8 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Еркін форма"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Компьютер"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Сыртқы дисплейге ауыстыру"</string>
-    <!-- no translation found for recent_task_option_close (942942499021777264) -->
-    <skip />
+    <string name="recent_task_option_close" msgid="942942499021777264">"Жабу"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Жұмыс үстелі"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Соңғы элементтер жоқ"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Қолданбаны пайдалану параметрлері"</string>
diff --git a/quickstep/res/values-kn/strings.xml b/quickstep/res/values-kn/strings.xml
index 19d3ec9..c43a3d1 100644
--- a/quickstep/res/values-kn/strings.xml
+++ b/quickstep/res/values-kn/strings.xml
@@ -23,8 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"ಮುಕ್ತಸ್ವರೂಪ"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"ಡೆಸ್ಕ್‌ಟಾಪ್"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"ಬಾಹ್ಯ ಡಿಸ್‌ಪ್ಲೇಗೆ ಸರಿಸಿ"</string>
-    <!-- no translation found for recent_task_option_close (942942499021777264) -->
-    <skip />
+    <string name="recent_task_option_close" msgid="942942499021777264">"ಮುಚ್ಚಿರಿ"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"ಡೆಸ್ಕ್‌ಟಾಪ್"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"ಯಾವುದೇ ಇತ್ತೀಚಿನ ಐಟಂಗಳಿಲ್ಲ"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ಆ್ಯಪ್‌ ಬಳಕೆಯ ಸೆಟ್ಟಿಂಗ್‌ಗಳು"</string>
diff --git a/quickstep/res/values-ko/strings.xml b/quickstep/res/values-ko/strings.xml
index 2e89174..4105f97 100644
--- a/quickstep/res/values-ko/strings.xml
+++ b/quickstep/res/values-ko/strings.xml
@@ -23,8 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"자유 형식"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"데스크톱"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"외부 디스플레이로 이동"</string>
-    <!-- no translation found for recent_task_option_close (942942499021777264) -->
-    <skip />
+    <string name="recent_task_option_close" msgid="942942499021777264">"닫기"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"데스크톱"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"최근 항목이 없습니다."</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"앱 사용 설정"</string>
diff --git a/quickstep/res/values-ky/strings.xml b/quickstep/res/values-ky/strings.xml
index ac2b06d..3109069 100644
--- a/quickstep/res/values-ky/strings.xml
+++ b/quickstep/res/values-ky/strings.xml
@@ -23,8 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Эркин форма режими"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Компьютер"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Тышкы экранга жылдыруу"</string>
-    <!-- no translation found for recent_task_option_close (942942499021777264) -->
-    <skip />
+    <string name="recent_task_option_close" msgid="942942499021777264">"Жабуу"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Компьютер"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Акыркы колдонмолор жок"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Колдонмону пайдалануу параметрлери"</string>
diff --git a/quickstep/res/values-lo/strings.xml b/quickstep/res/values-lo/strings.xml
index 2ebaa7a..3df475f 100644
--- a/quickstep/res/values-lo/strings.xml
+++ b/quickstep/res/values-lo/strings.xml
@@ -90,7 +90,7 @@
     <string name="allset_title" msgid="5021126669778966707">"ຮຽບຮ້ອຍໝົດແລ້ວ!"</string>
     <string name="allset_hint" msgid="459504134589971527">"ປັດຂຶ້ນເພື່ອໄປຫາໜ້າຫຼັກ"</string>
     <string name="allset_button_hint" msgid="2395219947744706291">"ແຕະປຸ່ມໜ້າທຳອິດເພື່ອໄປຫາໂຮມສະກຣີນຂອງທ່ານ"</string>
-    <string name="allset_description_generic" msgid="5385500062202019855">"ທ່ານເລີ່ມໃຊ້ແທັບເລັດ <xliff:g id="DEVICE">%1$s</xliff:g> ຂອງທ່ານໄດ້ແລ້ວ"</string>
+    <string name="allset_description_generic" msgid="5385500062202019855">"ທ່ານເລີ່ມໃຊ້<xliff:g id="DEVICE">%1$s</xliff:g>ຂອງທ່ານໄດ້ແລ້ວ"</string>
     <string name="default_device_name" msgid="6660656727127422487">"ອຸປະກອນ"</string>
     <string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"ການຕັ້ງຄ່າການນຳທາງລະບົບ"</annotation></string>
     <string name="action_share" msgid="2648470652637092375">"ແບ່ງປັນ"</string>
diff --git a/quickstep/res/values-lt/strings.xml b/quickstep/res/values-lt/strings.xml
index b57fee4..ec16b11 100644
--- a/quickstep/res/values-lt/strings.xml
+++ b/quickstep/res/values-lt/strings.xml
@@ -23,8 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Laisva forma"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Stalinis kompiuteris"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Perkelkite į išorinį ekraną"</string>
-    <!-- no translation found for recent_task_option_close (942942499021777264) -->
-    <skip />
+    <string name="recent_task_option_close" msgid="942942499021777264">"Uždaryti"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Stalinis kompiuteris"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Nėra jokių naujausių elementų"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Programos naudojimo nustatymai"</string>
diff --git a/quickstep/res/values-lv/strings.xml b/quickstep/res/values-lv/strings.xml
index 07e9e1a..0f15a23 100644
--- a/quickstep/res/values-lv/strings.xml
+++ b/quickstep/res/values-lv/strings.xml
@@ -23,8 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Brīva forma"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Dators"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Pārvietošana uz ārējo displeju"</string>
-    <!-- no translation found for recent_task_option_close (942942499021777264) -->
-    <skip />
+    <string name="recent_task_option_close" msgid="942942499021777264">"Aizvērt"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Darbvirsma"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Nav nesenu vienumu."</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Lietotņu izmantošanas iestatījumi"</string>
diff --git a/quickstep/res/values-mk/strings.xml b/quickstep/res/values-mk/strings.xml
index 0a133a3..8fd0cc5 100644
--- a/quickstep/res/values-mk/strings.xml
+++ b/quickstep/res/values-mk/strings.xml
@@ -23,8 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Работна површина"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Префрлете се на надворешниот екран"</string>
-    <!-- no translation found for recent_task_option_close (942942499021777264) -->
-    <skip />
+    <string name="recent_task_option_close" msgid="942942499021777264">"Затвори"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"За компјутер"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Нема неодамнешни ставки"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Поставки за користење на апликациите"</string>
diff --git a/quickstep/res/values-mn/strings.xml b/quickstep/res/values-mn/strings.xml
index c50e87a..f086314 100644
--- a/quickstep/res/values-mn/strings.xml
+++ b/quickstep/res/values-mn/strings.xml
@@ -23,8 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Чөлөөтэй хувьсах"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Компьютер"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Гадаад дэлгэц рүү зөөх"</string>
-    <!-- no translation found for recent_task_option_close (942942499021777264) -->
-    <skip />
+    <string name="recent_task_option_close" msgid="942942499021777264">"Хаах"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Дэлгэц"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Сүүлийн үеийн зүйл алга"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Апп ашиглалтын тохиргоо"</string>
diff --git a/quickstep/res/values-mr/strings.xml b/quickstep/res/values-mr/strings.xml
index 63f4317..75c42ad 100644
--- a/quickstep/res/values-mr/strings.xml
+++ b/quickstep/res/values-mr/strings.xml
@@ -23,8 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"फ्रीफॉर्म"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"डेस्कटॉप"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"बाह्य डिस्प्लेवर हलवा"</string>
-    <!-- no translation found for recent_task_option_close (942942499021777264) -->
-    <skip />
+    <string name="recent_task_option_close" msgid="942942499021777264">"बंद करा"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"डेस्कटॉप"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"कोणतेही अलीकडील आयटम नाहीत"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"अ‍ॅप वापर सेटिंग्ज"</string>
diff --git a/quickstep/res/values-my/strings.xml b/quickstep/res/values-my/strings.xml
index 9df3d66..81a10b5 100644
--- a/quickstep/res/values-my/strings.xml
+++ b/quickstep/res/values-my/strings.xml
@@ -23,8 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"အလွတ်ပုံစံ"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"ဒက်စ်တော့"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"ပြင်ပဖန်သားပြင်သို့ ရွှေ့ရန်"</string>
-    <!-- no translation found for recent_task_option_close (942942499021777264) -->
-    <skip />
+    <string name="recent_task_option_close" msgid="942942499021777264">"ပိတ်ရန်"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"ဒက်စ်တော့"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"မကြာမီကဖွင့်ထားသည်များ မရှိပါ"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"အက်ပ်အသုံးပြုမှု ဆက်တင်များ"</string>
diff --git a/quickstep/res/values-nb/strings.xml b/quickstep/res/values-nb/strings.xml
index d05884f..4c1aaaa 100644
--- a/quickstep/res/values-nb/strings.xml
+++ b/quickstep/res/values-nb/strings.xml
@@ -23,8 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Fritt format"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Skrivebord"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Flytt til ekstern skjerm"</string>
-    <!-- no translation found for recent_task_option_close (942942499021777264) -->
-    <skip />
+    <string name="recent_task_option_close" msgid="942942499021777264">"Lukk"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Skrivebord"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Ingen nylige elementer"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Innstillinger for appbruk"</string>
diff --git a/quickstep/res/values-ne/strings.xml b/quickstep/res/values-ne/strings.xml
index 6efdca9..47b2532 100644
--- a/quickstep/res/values-ne/strings.xml
+++ b/quickstep/res/values-ne/strings.xml
@@ -23,8 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"फ्रिफर्म"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"डेस्कटप"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"सारेर बाह्य डिस्प्लेमा लैजानुहोस्"</string>
-    <!-- no translation found for recent_task_option_close (942942499021777264) -->
-    <skip />
+    <string name="recent_task_option_close" msgid="942942499021777264">"बन्द गर्नुहोस्"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"डेस्कटप"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"हालसालैको कुनै पनि सामग्री छैन"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"एपको उपयोगका सेटिङहरू"</string>
diff --git a/quickstep/res/values-or/strings.xml b/quickstep/res/values-or/strings.xml
index 66cbcdd..248d4cc 100644
--- a/quickstep/res/values-or/strings.xml
+++ b/quickstep/res/values-or/strings.xml
@@ -23,8 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"ଫ୍ରିଫର୍ମ"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"ଡେସ୍କଟପ"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"ଏକ୍ସଟର୍ନଲ ଡିସପ୍ଲେକୁ ମୁଭ କରନ୍ତୁ"</string>
-    <!-- no translation found for recent_task_option_close (942942499021777264) -->
-    <skip />
+    <string name="recent_task_option_close" msgid="942942499021777264">"ବନ୍ଦ କରନ୍ତୁ"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"ଡେସ୍କଟପ"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"ବର୍ତ୍ତମାନର କୌଣସି ଆଇଟମ ନାହିଁ"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ଆପ ବ୍ୟବହାର ସେଟିଂସ"</string>
diff --git a/quickstep/res/values-pl/strings.xml b/quickstep/res/values-pl/strings.xml
index b571ba2..5c76187 100644
--- a/quickstep/res/values-pl/strings.xml
+++ b/quickstep/res/values-pl/strings.xml
@@ -23,8 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Tryb dowolny"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Pulpit"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Przenieś na wyświetlacz zewnętrzny"</string>
-    <!-- no translation found for recent_task_option_close (942942499021777264) -->
-    <skip />
+    <string name="recent_task_option_close" msgid="942942499021777264">"Zamknij"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Pulpit"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Brak ostatnich elementów"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Ustawienia użycia aplikacji"</string>
diff --git a/quickstep/res/values-pt/strings.xml b/quickstep/res/values-pt/strings.xml
index bb7d97d..ede50cc 100644
--- a/quickstep/res/values-pt/strings.xml
+++ b/quickstep/res/values-pt/strings.xml
@@ -23,8 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Forma livre"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Modo área de trabalho"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Mover para a tela externa"</string>
-    <!-- no translation found for recent_task_option_close (942942499021777264) -->
-    <skip />
+    <string name="recent_task_option_close" msgid="942942499021777264">"Fechar"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Computador"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Nenhum item recente"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Configurações de uso do app"</string>
@@ -91,7 +90,7 @@
     <string name="allset_title" msgid="5021126669778966707">"Tudo pronto!"</string>
     <string name="allset_hint" msgid="459504134589971527">"Deslize para cima para acessar a tela inicial"</string>
     <string name="allset_button_hint" msgid="2395219947744706291">"Toque no botão home para acessar a tela inicial"</string>
-    <string name="allset_description_generic" msgid="5385500062202019855">"Você já pode começar a usar seu <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
+    <string name="allset_description_generic" msgid="5385500062202019855">"O <xliff:g id="DEVICE">%1$s</xliff:g> já pode ser usado"</string>
     <string name="default_device_name" msgid="6660656727127422487">"dispositivo"</string>
     <string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Configurações de navegação do sistema"</annotation></string>
     <string name="action_share" msgid="2648470652637092375">"Compartilhar"</string>
diff --git a/quickstep/res/values-ro/strings.xml b/quickstep/res/values-ro/strings.xml
index 856bef7..a89ccdc 100644
--- a/quickstep/res/values-ro/strings.xml
+++ b/quickstep/res/values-ro/strings.xml
@@ -23,8 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Formă liberă"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Mută pe ecranul extern"</string>
-    <!-- no translation found for recent_task_option_close (942942499021777264) -->
-    <skip />
+    <string name="recent_task_option_close" msgid="942942499021777264">"Închide"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Computer"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Niciun element recent"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Setări de utilizare a aplicației"</string>
diff --git a/quickstep/res/values-si/strings.xml b/quickstep/res/values-si/strings.xml
index 3b1cada..de23b6e 100644
--- a/quickstep/res/values-si/strings.xml
+++ b/quickstep/res/values-si/strings.xml
@@ -23,8 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"ඩෙස්ක්ටොපය"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"බාහිර සංදර්ශකය වෙත ගෙන යන්න"</string>
-    <!-- no translation found for recent_task_option_close (942942499021777264) -->
-    <skip />
+    <string name="recent_task_option_close" msgid="942942499021777264">"වසන්න"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"ඩෙස්ක්ටොපය"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"මෑත අයිතම නැත"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"යෙදුම් භාවිත සැකසීම්"</string>
diff --git a/quickstep/res/values-sq/strings.xml b/quickstep/res/values-sq/strings.xml
index 8f39f65..9aedee5 100644
--- a/quickstep/res/values-sq/strings.xml
+++ b/quickstep/res/values-sq/strings.xml
@@ -23,8 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Formë e lirë"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktopi"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Zhvendose tek ekrani i jashtëm"</string>
-    <!-- no translation found for recent_task_option_close (942942499021777264) -->
-    <skip />
+    <string name="recent_task_option_close" msgid="942942499021777264">"Mbyll"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Desktopi"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Nuk ka asnjë artikull të fundit"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Cilësimet e përdorimit të aplikacionit"</string>
diff --git a/quickstep/res/values-sv/strings.xml b/quickstep/res/values-sv/strings.xml
index c33de8a..ab9f10e 100644
--- a/quickstep/res/values-sv/strings.xml
+++ b/quickstep/res/values-sv/strings.xml
@@ -23,8 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Fritt format"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Skrivbordsläge"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Flytta till extern skärm"</string>
-    <!-- no translation found for recent_task_option_close (942942499021777264) -->
-    <skip />
+    <string name="recent_task_option_close" msgid="942942499021777264">"Stäng"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Skrivbordsläge"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Listan är tom"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Inställningar för appanvändning"</string>
diff --git a/quickstep/res/values-sw/strings.xml b/quickstep/res/values-sw/strings.xml
index 543663f..5f696c5 100644
--- a/quickstep/res/values-sw/strings.xml
+++ b/quickstep/res/values-sw/strings.xml
@@ -23,8 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Muundo huru"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Kompyuta ya mezani"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Hamishia programu kwenye skrini ya nje"</string>
-    <!-- no translation found for recent_task_option_close (942942499021777264) -->
-    <skip />
+    <string name="recent_task_option_close" msgid="942942499021777264">"Funga"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Kompyuta ya Mezani"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Hakuna vipengee vya hivi karibuni"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Mipangilio ya matumizi ya programu"</string>
diff --git a/quickstep/res/values-ta/strings.xml b/quickstep/res/values-ta/strings.xml
index 1e6cb9b..4f22a04 100644
--- a/quickstep/res/values-ta/strings.xml
+++ b/quickstep/res/values-ta/strings.xml
@@ -23,8 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"குறிப்பிட்ட வடிவமில்லாத பயன்முறை"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"டெஸ்க்டாப்"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"வெளிப்புற டிஸ்ப்ளேவிற்கு நகர்த்துதல்"</string>
-    <!-- no translation found for recent_task_option_close (942942499021777264) -->
-    <skip />
+    <string name="recent_task_option_close" msgid="942942499021777264">"மூடு"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"டெஸ்க்டாப்"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"சமீபத்தியவை எதுவுமில்லை"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ஆப்ஸ் உபயோக அமைப்புகள்"</string>
diff --git a/quickstep/res/values-te/strings.xml b/quickstep/res/values-te/strings.xml
index 74e59ba..429d2d4 100644
--- a/quickstep/res/values-te/strings.xml
+++ b/quickstep/res/values-te/strings.xml
@@ -23,8 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"సంప్రదాయేతర"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"డెస్క్‌టాప్"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"ఎక్స్‌టర్నల్ డిస్‌ప్లేకు తరలించండి"</string>
-    <!-- no translation found for recent_task_option_close (942942499021777264) -->
-    <skip />
+    <string name="recent_task_option_close" msgid="942942499021777264">"మూసివేయండి"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"డెస్క్‌టాప్"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"ఇటీవలి ఐటెమ్‌లు ఏవీ లేవు"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"యాప్ వినియోగ సెట్టింగ్‌లు"</string>
diff --git a/quickstep/res/values-tr/strings.xml b/quickstep/res/values-tr/strings.xml
index ac06369..e213b40 100644
--- a/quickstep/res/values-tr/strings.xml
+++ b/quickstep/res/values-tr/strings.xml
@@ -23,8 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Serbest çalışma"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Masaüstü"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Harici ekrana taşı"</string>
-    <!-- no translation found for recent_task_option_close (942942499021777264) -->
-    <skip />
+    <string name="recent_task_option_close" msgid="942942499021777264">"Kapat"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Masaüstü"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Yeni öğe yok"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Uygulama kullanım ayarları"</string>
@@ -91,7 +90,7 @@
     <string name="allset_title" msgid="5021126669778966707">"Kurulum tamamlandı"</string>
     <string name="allset_hint" msgid="459504134589971527">"Ana ekrana gitmek için yukarı kaydırın"</string>
     <string name="allset_button_hint" msgid="2395219947744706291">"Ana ekranınıza gitmek için ana sayfa düğmesine dokunun"</string>
-    <string name="allset_description_generic" msgid="5385500062202019855">"<xliff:g id="DEVICE">%1$s</xliff:g> adlı cihazınızı kullanmaya hazırsınız"</string>
+    <string name="allset_description_generic" msgid="5385500062202019855">"Artık <xliff:g id="DEVICE">%1$s</xliff:g> kullanılmak için hazır"</string>
     <string name="default_device_name" msgid="6660656727127422487">"cihaz"</string>
     <string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Sistem gezinme ayarları"</annotation></string>
     <string name="action_share" msgid="2648470652637092375">"Paylaş"</string>
diff --git a/quickstep/res/values-uk/strings.xml b/quickstep/res/values-uk/strings.xml
index 2d27bb9..dab58d7 100644
--- a/quickstep/res/values-uk/strings.xml
+++ b/quickstep/res/values-uk/strings.xml
@@ -23,8 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Довільна форма"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Робочий стіл"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Перемістити на зовнішній екран"</string>
-    <!-- no translation found for recent_task_option_close (942942499021777264) -->
-    <skip />
+    <string name="recent_task_option_close" msgid="942942499021777264">"Закрити"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Комп’ютер"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Немає нещодавніх додатків"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Налаштування використання додатка"</string>
diff --git a/quickstep/res/values-vi/strings.xml b/quickstep/res/values-vi/strings.xml
index e49f7e3..bf50e05 100644
--- a/quickstep/res/values-vi/strings.xml
+++ b/quickstep/res/values-vi/strings.xml
@@ -23,8 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Dạng tự do"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Máy tính"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Chuyển sang màn hình ngoài"</string>
-    <!-- no translation found for recent_task_option_close (942942499021777264) -->
-    <skip />
+    <string name="recent_task_option_close" msgid="942942499021777264">"Đóng"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Máy tính"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Không có mục gần đây nào"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Cài đặt mức sử dụng ứng dụng"</string>
diff --git a/quickstep/res/values-zh-rCN/strings.xml b/quickstep/res/values-zh-rCN/strings.xml
index c1cf2fe..374a4b6 100644
--- a/quickstep/res/values-zh-rCN/strings.xml
+++ b/quickstep/res/values-zh-rCN/strings.xml
@@ -23,8 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"自由窗口"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"桌面"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"移至外接显示屏"</string>
-    <!-- no translation found for recent_task_option_close (942942499021777264) -->
-    <skip />
+    <string name="recent_task_option_close" msgid="942942499021777264">"关闭"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"桌面设备"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"近期没有任何内容"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"应用使用设置"</string>
diff --git a/quickstep/res/values-zh-rHK/strings.xml b/quickstep/res/values-zh-rHK/strings.xml
index 393565a..42e2e37 100644
--- a/quickstep/res/values-zh-rHK/strings.xml
+++ b/quickstep/res/values-zh-rHK/strings.xml
@@ -23,8 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"自由形式"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"桌面"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"移至外部顯示屏"</string>
-    <!-- no translation found for recent_task_option_close (942942499021777264) -->
-    <skip />
+    <string name="recent_task_option_close" msgid="942942499021777264">"關閉"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"桌面"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"最近沒有任何項目"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"應用程式使用情況設定"</string>
diff --git a/quickstep/res/values-zh-rTW/strings.xml b/quickstep/res/values-zh-rTW/strings.xml
index fbd465d..d9d1fb5 100644
--- a/quickstep/res/values-zh-rTW/strings.xml
+++ b/quickstep/res/values-zh-rTW/strings.xml
@@ -23,8 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"自由形式"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"電腦模式"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"移至外接螢幕"</string>
-    <!-- no translation found for recent_task_option_close (942942499021777264) -->
-    <skip />
+    <string name="recent_task_option_close" msgid="942942499021777264">"關閉"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"電腦模式"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"最近沒有任何項目"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"應用程式使用情況設定"</string>
diff --git a/quickstep/res/values-zu/strings.xml b/quickstep/res/values-zu/strings.xml
index 39b4873..660ef3e 100644
--- a/quickstep/res/values-zu/strings.xml
+++ b/quickstep/res/values-zu/strings.xml
@@ -23,8 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"I-Freeform"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Ideskithophu"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Hambisa esibonisini sangaphandle"</string>
-    <!-- no translation found for recent_task_option_close (942942499021777264) -->
-    <skip />
+    <string name="recent_task_option_close" msgid="942942499021777264">"Vala"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Ideskithophu"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Azikho izinto zakamuva"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Izilungiselelo zokusetshenziswa kohlelo lokusebenza"</string>
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 4ba4e2b..c748385 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -411,7 +411,8 @@
             @NonNull RemoteAnimationTarget[] nonAppTargets, boolean launcherClosing) {
         TaskViewUtils.composeRecentsLaunchAnimator(anim, v, appTargets, wallpaperTargets,
                 nonAppTargets, launcherClosing, mLauncher.getStateManager(),
-                mLauncher.getOverviewPanel(), mLauncher.getDepthController());
+                mLauncher.getOverviewPanel(), mLauncher.getDepthController(),
+                /* transitionInfo= */ null);
     }
 
     private boolean areAllTargetsTranslucent(@NonNull RemoteAnimationTarget[] targets) {
diff --git a/quickstep/src/com/android/launcher3/dagger/Modules.kt b/quickstep/src/com/android/launcher3/dagger/Modules.kt
index 52be413..99631bf 100644
--- a/quickstep/src/com/android/launcher3/dagger/Modules.kt
+++ b/quickstep/src/com/android/launcher3/dagger/Modules.kt
@@ -19,8 +19,10 @@
 import com.android.launcher3.uioverrides.SystemApiWrapper
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapperImpl
 import com.android.launcher3.util.ApiWrapper
+import com.android.launcher3.util.PerDisplayObjectProvider
 import com.android.launcher3.util.PluginManagerWrapper
 import com.android.launcher3.util.window.WindowManagerProxy
+import com.android.quickstep.fallback.window.RecentsDisplayModel
 import com.android.quickstep.util.GestureExclusionManager
 import com.android.quickstep.util.SystemWindowManagerProxy
 import dagger.Binds
@@ -52,3 +54,9 @@
     @JvmStatic
     fun provideGestureExclusionManager(): GestureExclusionManager = GestureExclusionManager.INSTANCE
 }
+
+@Module
+abstract class PerDisplayObjectProviderModule {
+    @Binds
+    abstract fun bindPerDisplayObjectProvider(impl: RecentsDisplayModel): PerDisplayObjectProvider
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 4143157..3f1bf3c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -202,7 +202,7 @@
     @Override
     public void onLauncherVisibilityChanged(boolean isVisible) {
         if (DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(mLauncher)) {
-            DisplayController.INSTANCE.get(mLauncher).notifyConfigChange();
+            DisplayController.get(mLauncher).notifyConfigChange();
         }
         onLauncherVisibilityChanged(isVisible, false /* fromInit */);
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index a6d3cde..27f3b8e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -47,6 +47,7 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING;
 import static com.android.window.flags.Flags.enableStartLaunchTransitionFromTaskbarBugfix;
+import static com.android.window.flags.Flags.enableTaskbarConnectedDisplays;
 import static com.android.wm.shell.Flags.enableTinyTaskbar;
 
 import static java.lang.invoke.MethodHandles.Lookup.PROTECTED;
@@ -324,9 +325,9 @@
                     new BubbleDragController(this),
                     new BubbleDismissController(this, mDragLayer),
                     new BubbleBarPinController(this, mDragLayer,
-                            () -> DisplayController.INSTANCE.get(this).getInfo().currentSize),
+                            () -> DisplayController.get(this).getInfo().currentSize),
                     new BubblePinController(this, mDragLayer,
-                            () -> DisplayController.INSTANCE.get(this).getInfo().currentSize),
+                            () -> DisplayController.get(this).getInfo().currentSize),
                     bubbleBarSwipeController,
                     new BubbleCreator(this)
             ));
@@ -433,7 +434,9 @@
                     .setIsTransientTaskbar(true)
                     .build();
         }
-        mNavMode = DisplayController.getNavigationMode(this);
+        mNavMode = (enableTaskbarConnectedDisplays() && !mIsPrimaryDisplay)
+                ? NavigationMode.THREE_BUTTONS : DisplayController.getNavigationMode(this);
+
     }
 
     /** Called when the visibility of the bubble bar changed. */
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index 66f19eb..4dc92c9 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -71,7 +71,6 @@
 import com.android.launcher3.util.SimpleBroadcastReceiver;
 import com.android.quickstep.AllAppsActionManager;
 import com.android.quickstep.RecentsActivity;
-import com.android.quickstep.RecentsAnimationDeviceState;
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.fallback.window.RecentsDisplayModel;
 import com.android.quickstep.fallback.window.RecentsWindowManager;
@@ -85,7 +84,6 @@
 
 import java.io.PrintWriter;
 import java.util.StringJoiner;
-import java.util.function.LongConsumer;
 
 /**
  * Class to manage taskbar lifecycle
@@ -246,8 +244,6 @@
                 }
             };
 
-    private final LongConsumer mSysUiFlagChangeReceiver = this::onSystemUiFlagsChanged;
-
     @SuppressLint("WrongConstant")
     public TaskbarManager(
             Context context,
@@ -282,10 +278,6 @@
                     mPrimaryWindowContext, RECEIVER_NOT_EXPORTED, ACTION_SHOW_TASKBAR);
         });
 
-        RecentsAnimationDeviceState deviceState = RecentsAnimationDeviceState.INSTANCE.get(context);
-        onSystemUiFlagsChanged(deviceState.getSystemUiStateFlags());
-        deviceState.addSysUiFlagChangeCallback(mSysUiFlagChangeReceiver);
-
         debugWhyTaskbarNotDestroyed("TaskbarManager created");
         recreateTaskbar();
     }
@@ -433,7 +425,7 @@
      */
     public void onUserUnlocked() {
         mUserUnlocked = true;
-        DisplayController.INSTANCE.get(mPrimaryWindowContext).addChangeListener(
+        DisplayController.get(mPrimaryWindowContext).addChangeListener(
                 mRecreationListener);
         recreateTaskbar();
         addTaskbarRootViewToWindow(getDefaultDisplayId());
@@ -614,7 +606,7 @@
         }
     }
 
-    private void onSystemUiFlagsChanged(@SystemUiStateFlags long systemUiStateFlags) {
+    public void onSystemUiFlagsChanged(@SystemUiStateFlags long systemUiStateFlags) {
         if (DEBUG) {
             Log.d(TAG, "SysUI flags changed: " + formatFlagChange(systemUiStateFlags,
                     mSharedState.sysuiStateFlags, QuickStepContract::getSystemUiStateString));
@@ -637,6 +629,7 @@
      */
     public void setSetupUIVisible(boolean isVisible) {
         mSharedState.setupUIVisible = isVisible;
+        mAllAppsActionManager.setSetupUiVisible(isVisible);
         TaskbarActivityContext taskbar = getTaskbarForDisplay(getDefaultDisplayId());
         if (taskbar != null) {
             taskbar.setSetupUIVisible(isVisible);
@@ -805,15 +798,13 @@
         mTaskbarBroadcastReceiver.unregisterReceiverSafely(mPrimaryWindowContext);
 
         if (mUserUnlocked) {
-            DisplayController.INSTANCE.get(mPrimaryWindowContext).removeChangeListener(
+            DisplayController.get(mPrimaryWindowContext).removeChangeListener(
                     mRecreationListener);
         }
         SettingsCache.INSTANCE.get(mPrimaryWindowContext)
                 .unregister(USER_SETUP_COMPLETE_URI, mOnSettingsChangeListener);
         SettingsCache.INSTANCE.get(mPrimaryWindowContext)
                 .unregister(NAV_BAR_KIDS_MODE, mOnSettingsChangeListener);
-        RecentsAnimationDeviceState.INSTANCE.get(getPrimaryWindowContext())
-                .removeSysUiFlagChangeCallback(mSysUiFlagChangeReceiver);
         Log.d(TASKBAR_NOT_DESTROYED_TAG, "unregistering component callbacks from destroy().");
         mPrimaryWindowContext.unregisterComponentCallbacks(mDefaultComponentCallbacks);
         mShutdownReceiver.unregisterReceiverSafely(mPrimaryWindowContext);
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
index c001123..5025afc 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
@@ -623,7 +623,7 @@
      * Get bubble bar top coordinate on screen when bar is resting
      */
     public int getRestingTopPositionOnScreen() {
-        int displayHeight = DisplayController.INSTANCE.get(getContext()).getInfo().currentSize.y;
+        int displayHeight = DisplayController.get(getContext()).getInfo().currentSize.y;
         int bubbleBarHeight = getBubbleBarBounds().height();
         return displayHeight - bubbleBarHeight + (int) mController.getBubbleBarTranslationY();
     }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 6eec7d5..23f4f67 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -172,12 +172,10 @@
 import com.android.quickstep.OverviewCommandHelper;
 import com.android.quickstep.OverviewComponentObserver;
 import com.android.quickstep.OverviewComponentObserver.OverviewChangeListener;
-import com.android.quickstep.RecentsAnimationDeviceState;
-import com.android.quickstep.RecentsAnimationDeviceState.AssistantVisibilityChangeListener;
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.SystemUiProxy;
-import com.android.quickstep.TISBinder;
 import com.android.quickstep.TaskUtils;
+import com.android.quickstep.TouchInteractionService.TISBinder;
 import com.android.quickstep.util.ActiveGestureProtoLogProxy;
 import com.android.quickstep.util.AsyncClockEventDelegate;
 import com.android.quickstep.util.LauncherUnfoldAnimationController;
@@ -224,7 +222,7 @@
 import java.util.stream.Stream;
 
 public class QuickstepLauncher extends Launcher implements RecentsViewContainer,
-        SystemShortcut.BubbleActivityStarter, AssistantVisibilityChangeListener {
+        SystemShortcut.BubbleActivityStarter {
     private static final boolean TRACE_LAYOUTS =
             SystemProperties.getBoolean("persist.debug.trace_layouts", false);
     private static final String TRACE_RELAYOUT_CLASS =
@@ -595,8 +593,6 @@
         mHotseatPredictionController.destroy();
         if (mViewCapture != null) mViewCapture.close();
         removeBackAnimationCallback(mSplitSelectStateController.getSplitBackHandler());
-        RecentsAnimationDeviceState.INSTANCE.get(this)
-                .removeAssistantVisibilityChangeListener(this);
     }
 
     @Override
@@ -730,7 +726,6 @@
         View.setTracedRequestLayoutClassClass(TRACE_RELAYOUT_CLASS);
         OverviewComponentObserver.INSTANCE.get(this)
                 .addOverviewChangeListener(mOverviewChangeListener);
-        RecentsAnimationDeviceState.INSTANCE.get(this).addAssistantVisibilityChangeListener(this);
     }
 
     @Override
@@ -1090,6 +1085,7 @@
         if (taskbarManager != null) {
             taskbarManager.setActivity(this);
         }
+        mTISBindHelper.setPredictiveBackToHomeInProgress(mIsPredictiveBackToHomeInProgress);
     }
 
     @Override
@@ -1363,8 +1359,7 @@
      */
     public void setPredictiveBackToHomeInProgress(boolean isInProgress) {
         mIsPredictiveBackToHomeInProgress = isInProgress;
-        RecentsAnimationDeviceState.INSTANCE.get(this)
-                .setPredictiveBackToHomeInProgress(isInProgress);
+        mTISBindHelper.setPredictiveBackToHomeInProgress(isInProgress);
     }
 
     public boolean getPredictiveBackToHomeInProgress() {
@@ -1506,11 +1501,6 @@
         return getStateManager().getState().isRecentsViewVisible;
     }
 
-    @Override
-    public void onAssistantVisibilityChanged(float visibility) {
-        mHotseat.getQsb().setAlpha(1f - visibility);
-    }
-
     public boolean isCanShowAllAppsEducationView() {
         return mCanShowAllAppsEducationView;
     }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
index 865e16b..ff726e6 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
@@ -28,6 +28,8 @@
 import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback;
 import static com.android.launcher3.util.NavigationMode.THREE_BUTTONS;
 import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ONE_HANDED_ACTIVE;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
 
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
@@ -45,7 +47,7 @@
 import com.android.launcher3.uioverrides.QuickstepLauncher;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.VibratorWrapper;
-import com.android.quickstep.RecentsAnimationDeviceState;
+import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.util.AnimatorControllerWithResistance;
 import com.android.quickstep.util.MotionPauseDetector;
 import com.android.quickstep.util.OverviewToHomeAnim;
@@ -111,8 +113,8 @@
             return false;
         }
         mDidTouchStartInNavBar = (ev.getEdgeFlags() & EDGE_NAV_BAR) != 0;
-        boolean isOneHandedModeActive =
-                RecentsAnimationDeviceState.INSTANCE.get(mLauncher).isOneHandedModeActive();
+        boolean isOneHandedModeActive = (SystemUiProxy.INSTANCE.get(mLauncher)
+                .getLastSystemUiStateFlags() & SYSUI_STATE_ONE_HANDED_ACTIVE) != 0;
         // Reset touch slop multiplier to default 1.0f if one-handed-mode is not active
         mDetector.setTouchSlopMultiplier(
                 isOneHandedModeActive ? ONE_HANDED_ACTIVATED_SLOP_MULTIPLIER : 1f /* default */);
@@ -248,8 +250,9 @@
     }
 
     private boolean handlingOverviewAnim() {
+        long stateFlags = SystemUiProxy.INSTANCE.get(mLauncher).getLastSystemUiStateFlags();
         return mDidTouchStartInNavBar && mStartState == NORMAL
-                && !RecentsAnimationDeviceState.INSTANCE.get(mLauncher).isOverviewDisabled();
+                && (stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) == 0;
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index 6bca4c7..05d12c3 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -55,6 +55,7 @@
 import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
 import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
 import static com.android.quickstep.views.RecentsView.TASK_THUMBNAIL_SPLASH_ALPHA;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
 
 import android.animation.Animator;
 import android.animation.Animator.AnimatorListener;
@@ -78,7 +79,7 @@
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.TouchController;
 import com.android.launcher3.util.VibratorWrapper;
-import com.android.quickstep.RecentsAnimationDeviceState;
+import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.util.AnimatorControllerWithResistance;
 import com.android.quickstep.util.LayoutUtils;
 import com.android.quickstep.util.MotionPauseDetector;
@@ -177,7 +178,8 @@
         if ((ev.getEdgeFlags() & Utilities.EDGE_NAV_BAR) == 0) {
             return false;
         }
-        if (RecentsAnimationDeviceState.INSTANCE.get(mLauncher).isOverviewDisabled()) {
+        long stateFlags = SystemUiProxy.INSTANCE.get(mLauncher).getLastSystemUiStateFlags();
+        if ((stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0) {
             return false;
         }
         if (isTrackpadMultiFingerSwipe(ev)) {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
index 7547f15..f582324 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
@@ -34,6 +34,7 @@
 import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
 import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
 
 import android.view.MotionEvent;
 
@@ -45,7 +46,7 @@
 import com.android.launcher3.uioverrides.QuickstepLauncher;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.NavigationMode;
-import com.android.quickstep.RecentsAnimationDeviceState;
+import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.TaskUtils;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
@@ -83,7 +84,8 @@
 
     @Override
     protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
-        if (RecentsAnimationDeviceState.INSTANCE.get(mLauncher).isOverviewDisabled()) {
+        long stateFlags = SystemUiProxy.INSTANCE.get(mLauncher).getLastSystemUiStateFlags();
+        if ((stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0) {
             return NORMAL;
         }
         return isDragTowardPositive ? QUICK_SWITCH_FROM_HOME : NORMAL;
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index f46f9ae..b43c3ac 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -2453,7 +2453,8 @@
     }
 
     @Override
-    public void onTasksAppeared(@NonNull RemoteAnimationTarget[] appearedTaskTargets) {
+    public void onTasksAppeared(@NonNull RemoteAnimationTarget[] appearedTaskTargets,
+            @Nullable TransitionInfo transitionInfo) {
         if (mRecentsAnimationController == null) {
             return;
         }
diff --git a/quickstep/src/com/android/quickstep/AllAppsActionManager.kt b/quickstep/src/com/android/quickstep/AllAppsActionManager.kt
index 6fd68d5..b807a4b 100644
--- a/quickstep/src/com/android/quickstep/AllAppsActionManager.kt
+++ b/quickstep/src/com/android/quickstep/AllAppsActionManager.kt
@@ -21,10 +21,16 @@
 import android.app.RemoteAction
 import android.content.Context
 import android.graphics.drawable.Icon
+import android.provider.Settings
+import android.provider.Settings.Secure.USER_SETUP_COMPLETE
 import android.view.accessibility.AccessibilityManager
 import com.android.launcher3.R
+import com.android.launcher3.util.SettingsCache
+import com.android.launcher3.util.SettingsCache.OnChangeListener
 import java.util.concurrent.Executor
 
+private val USER_SETUP_COMPLETE_URI = Settings.Secure.getUriFor(USER_SETUP_COMPLETE)
+
 /**
  * Registers a [RemoteAction] for toggling All Apps if needed.
  *
@@ -38,6 +44,12 @@
     private val createAllAppsPendingIntent: () -> PendingIntent,
 ) {
 
+    private val onSettingsChangeListener = OnChangeListener { v -> isUserSetupComplete = v }
+
+    init {
+        SettingsCache.INSTANCE[context].register(USER_SETUP_COMPLETE_URI, onSettingsChangeListener)
+    }
+
     /** `true` if home and overview are the same Activity. */
     var isHomeAndOverviewSame = false
         set(value) {
@@ -52,12 +64,27 @@
             updateSystemAction()
         }
 
+    /** `true` if the setup UI is visible. */
+    var isSetupUiVisible = false
+        set(value) {
+            field = value
+            updateSystemAction()
+        }
+
+    private var isUserSetupComplete =
+        SettingsCache.INSTANCE[context].getValue(USER_SETUP_COMPLETE_URI, 0)
+        set(value) {
+            field = value
+            updateSystemAction()
+        }
+
     /** `true` if the action should be registered. */
     var isActionRegistered = false
         private set
 
     private fun updateSystemAction() {
-        val shouldRegisterAction = isHomeAndOverviewSame || isTaskbarPresent
+        val isInSetupFlow = isSetupUiVisible || !isUserSetupComplete
+        val shouldRegisterAction = (isHomeAndOverviewSame || isTaskbarPresent) && !isInSetupFlow
         if (isActionRegistered == shouldRegisterAction) return
         isActionRegistered = shouldRegisterAction
 
@@ -84,8 +111,10 @@
         isActionRegistered = false
         context
             .getSystemService(AccessibilityManager::class.java)
-            ?.unregisterSystemAction(
-                GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS,
-            )
+            ?.unregisterSystemAction(GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS)
+        SettingsCache.INSTANCE[context].unregister(
+            USER_SETUP_COMPLETE_URI,
+            onSettingsChangeListener,
+        )
     }
 }
diff --git a/quickstep/src/com/android/quickstep/BaseContainerInterface.java b/quickstep/src/com/android/quickstep/BaseContainerInterface.java
index 82fbaa6..4ab104e 100644
--- a/quickstep/src/com/android/quickstep/BaseContainerInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseContainerInterface.java
@@ -83,6 +83,8 @@
 
     public abstract boolean isInLiveTileMode();
 
+    public abstract void onAssistantVisibilityChanged(float assistantVisibility);
+
     public abstract boolean isResumed();
 
     public abstract boolean isStarted();
@@ -288,7 +290,7 @@
             insets = dp.getInsets();
         } else {
             Rect portraitInsets = dp.getInsets();
-            DisplayController displayController = DisplayController.INSTANCE.get(context);
+            DisplayController displayController = DisplayController.get(context);
             @Nullable List<WindowBounds> windowBounds =
                     displayController.getInfo().getCurrentBounds();
             Rect deviceRotationInsets = windowBounds != null
diff --git a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
index d122d24..d8e0296 100644
--- a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
@@ -69,6 +69,14 @@
         }
     }
 
+    /** 5 */
+    @Override
+    public void onAssistantVisibilityChanged(float visibility) {
+        // This class becomes active when the screen is locked.
+        // Rather than having it handle assistant visibility changes, the assistant visibility is
+        // set to zero prior to this class becoming active.
+    }
+
     /** 6 */
     @Override
     public AnimationFactory prepareRecentsUI(
diff --git a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
index 331580c..7d8a53d 100644
--- a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -50,6 +50,7 @@
 import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
+import android.window.TransitionInfo;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -172,14 +173,15 @@
     }
 
     @Override
-    public void onTasksAppeared(@NonNull RemoteAnimationTarget[] appearedTaskTargets) {
+    public void onTasksAppeared(@NonNull RemoteAnimationTarget[] appearedTaskTargets,
+            @Nullable TransitionInfo transitionInfo) {
         if (mActiveAnimationFactory != null && mActiveAnimationFactory.handleHomeTaskAppeared(
                 appearedTaskTargets)) {
             mActiveAnimationFactory = null;
             return;
         }
 
-        super.onTasksAppeared(appearedTaskTargets);
+        super.onTasksAppeared(appearedTaskTargets, transitionInfo);
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/FallbackWindowInterface.java b/quickstep/src/com/android/quickstep/FallbackWindowInterface.java
index 33fe02e..35630ef 100644
--- a/quickstep/src/com/android/quickstep/FallbackWindowInterface.java
+++ b/quickstep/src/com/android/quickstep/FallbackWindowInterface.java
@@ -70,6 +70,14 @@
         }
     }
 
+    /** 5 */
+    @Override
+    public void onAssistantVisibilityChanged(float visibility) {
+        // This class becomes active when the screen is locked.
+        // Rather than having it handle assistant visibility changes, the assistant visibility is
+        // set to zero prior to this class becoming active.
+    }
+
     /** 6 */
     @Override
     public BaseWindowInterface.AnimationFactory prepareRecentsUI(boolean activityVisible,
diff --git a/quickstep/src/com/android/quickstep/InputConsumerUtils.kt b/quickstep/src/com/android/quickstep/InputConsumerUtils.kt
index 1a82d3b..c340c92 100644
--- a/quickstep/src/com/android/quickstep/InputConsumerUtils.kt
+++ b/quickstep/src/com/android/quickstep/InputConsumerUtils.kt
@@ -18,6 +18,7 @@
 import android.content.Context
 import android.view.MotionEvent
 import androidx.annotation.VisibleForTesting
+import com.android.launcher3.anim.AnimatedFloat
 import com.android.launcher3.statemanager.BaseState
 import com.android.launcher3.statemanager.StatefulContainer
 import com.android.launcher3.taskbar.TaskbarManager
@@ -46,6 +47,7 @@
 import com.android.systemui.shared.system.InputMonitorCompat
 import com.android.wm.shell.Flags
 import java.util.function.Consumer
+import java.util.function.Function
 
 /** Utility class for creating input consumers. */
 object InputConsumerUtils {
@@ -66,6 +68,7 @@
         onCompleteCallback: Consumer<OtherActivityInputConsumer>,
         inputEventReceiver: InputChannelCompat.InputEventReceiver,
         taskbarManager: TaskbarManager,
+        swipeUpProxyProvider: Function<GestureState?, AnimatedFloat?>,
         overviewCommandHelper: OverviewCommandHelper,
         event: MotionEvent,
     ): InputConsumer where T : RecentsViewContainer, T : StatefulContainer<S> {
@@ -80,7 +83,7 @@
             )
             return consumer
         }
-        val progressProxy = deviceState.getSwipeUpProxy(gestureState)
+        val progressProxy = swipeUpProxyProvider.apply(gestureState)
         if (progressProxy != null) {
             val consumer: InputConsumer =
                 ProgressDelegateInputConsumer(
diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index 2c37470..ac0aa76 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -97,6 +97,15 @@
     }
 
     @Override
+    public void onAssistantVisibilityChanged(float visibility) {
+        QuickstepLauncher launcher = getCreatedContainer();
+        if (launcher == null) {
+            return;
+        }
+        launcher.onAssistantVisibilityChanged(visibility);
+    }
+
+    @Override
     public AnimationFactory prepareRecentsUI(
             boolean activityVisible, Consumer<AnimatorControllerWithResistance> callback) {
         notifyRecentsOfOrientation();
diff --git a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
index a34b239..1f95c41 100644
--- a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
+++ b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
@@ -57,7 +57,6 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.function.LongConsumer;
 
 import javax.inject.Inject;
 
@@ -80,7 +79,6 @@
 
     private final Context mContext;
     private final RecentsDisplayModel mRecentsDisplayModel;
-    private final RecentsAnimationDeviceState mDeviceState;
 
     private final Intent mCurrentHomeIntent;
     private final Intent mMyHomeIntent;
@@ -102,11 +100,9 @@
     public OverviewComponentObserver(
             @ApplicationContext Context context,
             RecentsDisplayModel recentsDisplayModel,
-            RecentsAnimationDeviceState deviceState,
             DaggerSingletonTracker lifecycleTracker) {
         mContext = context;
         mRecentsDisplayModel = recentsDisplayModel;
-        mDeviceState = deviceState;
         mCurrentHomeIntent = createHomeIntent();
         mMyHomeIntent = new Intent(mCurrentHomeIntent).setPackage(mContext.getPackageName());
         ResolveInfo info = context.getPackageManager().resolveActivity(mMyHomeIntent, 0);
@@ -132,12 +128,6 @@
         updateOverviewTargets();
 
         lifecycleTracker.addCloseable(this::onDestroy);
-
-        setHomeDisabled(deviceState.isHomeDisabled());
-        LongConsumer flagChangeCallback = l -> setHomeDisabled(deviceState.isHomeDisabled());
-        deviceState.addSysUiFlagChangeCallback(flagChangeCallback);
-        lifecycleTracker.addCloseable(
-                () -> deviceState.removeSysUiFlagChangeCallback(flagChangeCallback));
     }
 
     /** Adds a listener for changes in {@link #isHomeAndOverviewSame()} */
@@ -150,7 +140,11 @@
         mOverviewChangeListeners.remove(overviewChangeListener);
     }
 
-    private void setHomeDisabled(boolean isHomeDisabled) {
+    /**
+     * Called to set home enabled/disabled state via systemUI
+     * @param isHomeDisabled
+     */
+    public void setHomeDisabled(boolean isHomeDisabled) {
         if (isHomeDisabled != mIsHomeDisabled) {
             mIsHomeDisabled = isHomeDisabled;
             updateOverviewTargets();
@@ -181,7 +175,9 @@
         // Set assistant visibility to 0 from launcher's perspective, ensures any elements that
         // launcher made invisible become visible again before the new activity control helper
         // becomes active.
-        mDeviceState.setAssistantVisibility(0f);
+        if (mContainerInterface != null) {
+            mContainerInterface.onAssistantVisibilityChanged(0.f);
+        }
 
         if (SEPARATE_RECENTS_ACTIVITY.get() || Flags.enableLauncherOverviewInWindow()) {
             mIsDefaultHome = false;
diff --git a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
index 90221f3..93f31ff 100644
--- a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -28,6 +28,7 @@
 import java.util.function.Consumer;
 import java.util.function.Function;
 
+@SuppressWarnings("VisibleForTests")
 public class QuickstepTestInformationHandler extends TestInformationHandler {
 
     protected final Context mContext;
@@ -172,7 +173,7 @@
                 return response;
 
             case TestProtocol.REQUEST_REFRESH_OVERVIEW_TARGET:
-                runOnTISBinder(TISBinder::refreshOverviewTarget);
+                runOnTISBinder(TouchInteractionService.TISBinder::refreshOverviewTarget);
                 return response;
 
             case TestProtocol.REQUEST_RECREATE_TASKBAR:
@@ -220,7 +221,7 @@
     }
 
     private void enableBlockingTimeout(
-            TISBinder tisBinder, boolean enable) {
+            TouchInteractionService.TISBinder tisBinder, boolean enable) {
         TaskbarActivityContext context = tisBinder.getTaskbarManager().getCurrentActivityContext();
         if (context == null) {
             return;
@@ -229,14 +230,14 @@
     }
 
     private void enableTransientTaskbar(boolean enable) {
-        DisplayController.INSTANCE.get(mContext).enableTransientTaskbarForTests(enable);
+        DisplayController.get(mContext).enableTransientTaskbarForTests(enable);
     }
 
     /**
      * Runs the given command on the UI thread, after ensuring we are connected to
      * TouchInteractionService.
      */
-    protected void runOnTISBinder(Consumer<TISBinder> connectionCallback) {
+    protected void runOnTISBinder(Consumer<TouchInteractionService.TISBinder> connectionCallback) {
         try {
             CountDownLatch countDownLatch = new CountDownLatch(1);
             TISBindHelper helper = MAIN_EXECUTOR.submit(() ->
@@ -252,7 +253,7 @@
     }
 
     private <T> Bundle getTISBinderUIProperty(
-            BundleSetter<T> bundleSetter, Function<TISBinder, T> provider) {
+            BundleSetter<T> bundleSetter, Function<TouchInteractionService.TISBinder, T> provider) {
         Bundle response = new Bundle();
 
         runOnTISBinder(tisBinder -> bundleSetter.set(
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index 760130e..3d12fdf 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -165,7 +165,7 @@
         mTISBindHelper = new TISBindHelper(this, this::onTISConnected);
     }
 
-    private void onTISConnected(TISBinder binder) {
+    private void onTISConnected(TouchInteractionService.TISBinder binder) {
         TaskbarManager taskbarManager = binder.getTaskbarManager();
         if (taskbarManager != null) {
             taskbarManager.setActivity(this);
@@ -319,7 +319,7 @@
     /**
      * Composes the animations for a launch from the recents list if possible.
      */
-    private AnimatorSet  composeRecentsLaunchAnimator(
+    private AnimatorSet composeRecentsLaunchAnimator(
             @NonNull RecentsView recentsView,
             @NonNull TaskView taskView,
             RemoteAnimationTarget[] appTargets,
@@ -329,7 +329,8 @@
         boolean activityClosing = taskIsATargetWithMode(appTargets, getTaskId(), MODE_CLOSING);
         PendingAnimation pa = new PendingAnimation(RECENTS_LAUNCH_DURATION);
         createRecentsWindowAnimator(recentsView, taskView, !activityClosing, appTargets,
-                wallpaperTargets, nonAppTargets, null /* depthController */, pa);
+                wallpaperTargets, nonAppTargets, /* depthController= */ null ,
+                /* transitionInfo= */ null, pa);
         target.play(pa.buildAnim());
 
         // Found a visible recents task that matches the opening app, lets launch the app from there
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
index c6b858b..c276447 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
@@ -163,11 +163,12 @@
 
     @BinderThread
     @Override
-    public void onTasksAppeared(RemoteAnimationTarget[] apps) {
+    public void onTasksAppeared(
+            RemoteAnimationTarget[] apps, @Nullable TransitionInfo transitionInfo) {
         Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
             ActiveGestureProtoLogProxy.logRecentsAnimationCallbacksOnTasksAppeared();
             for (RecentsAnimationListener listener : getListeners()) {
-                listener.onTasksAppeared(apps);
+                listener.onTasksAppeared(apps, transitionInfo);
             }
         });
     }
@@ -225,6 +226,7 @@
         /**
          * Callback made when a task started from the recents is ready for an app transition.
          */
-        default void onTasksAppeared(@NonNull RemoteAnimationTarget[] appearedTaskTarget) {}
+        default void onTasksAppeared(@NonNull RemoteAnimationTarget[] appearedTaskTarget,
+                @Nullable TransitionInfo transitionInfo) {}
     }
 }
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index 322e7c4..3742d5a 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -64,7 +64,6 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
-import com.android.launcher3.anim.AnimatedFloat;
 import com.android.launcher3.dagger.ApplicationContext;
 import com.android.launcher3.dagger.LauncherAppComponent;
 import com.android.launcher3.dagger.LauncherAppSingleton;
@@ -74,6 +73,7 @@
 import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
 import com.android.launcher3.util.DisplayController.Info;
 import com.android.launcher3.util.NavigationMode;
+import com.android.launcher3.util.PerDisplayObjectProvider;
 import com.android.launcher3.util.SettingsCache;
 import com.android.quickstep.TopTaskTracker.CachedTaskInfo;
 import com.android.quickstep.util.ActiveGestureLog;
@@ -89,10 +89,6 @@
 import com.android.systemui.shared.system.TaskStackChangeListeners;
 
 import java.io.PrintWriter;
-import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.function.Function;
-import java.util.function.LongConsumer;
 
 import javax.inject.Inject;
 
@@ -123,16 +119,12 @@
     private final boolean mCanImeRenderGesturalNavButtons =
             InputMethodService.canImeRenderGesturalNavButtons();
 
-    private final List<LongConsumer> mFlagChangeCallbacks = new CopyOnWriteArrayList<>();
     private @SystemUiStateFlags long mSystemUiStateFlags = QuickStepContract.SYSUI_STATE_AWAKE;
     private NavigationMode mMode = THREE_BUTTONS;
     private NavBarPosition mNavBarPosition;
 
     private final Region mDeferredGestureRegion = new Region();
     private boolean mAssistantAvailable;
-
-    private final List<AssistantVisibilityChangeListener> mAssistantVisibilityChangeListeners =
-            new CopyOnWriteArrayList<>();
     private float mAssistantVisibility;
     private boolean mIsUserSetupComplete;
     private boolean mIsOneHandedModeEnabled;
@@ -145,20 +137,18 @@
     private @NonNull Region mExclusionRegion = GestureExclusionManager.EMPTY_REGION;
     private boolean mExclusionListenerRegistered;
 
-    private Function<GestureState, AnimatedFloat> mSwipeUpProxyProvider = null;
-
     @VisibleForTesting
     @Inject
     RecentsAnimationDeviceState(
             @ApplicationContext Context context,
             GestureExclusionManager exclusionManager,
-            DisplayController displayController,
+            PerDisplayObjectProvider displayControllerProvider,
             ContextualSearchStateManager contextualSearchStateManager,
             RotationTouchHelper rotationTouchHelper,
             SettingsCache settingsCache,
             DaggerSingletonTracker lifeCycle) {
         mContext = context;
-        mDisplayController = displayController;
+        mDisplayController = displayControllerProvider.getDisplayController(DEFAULT_DISPLAY);
         mExclusionManager = exclusionManager;
         mContextualSearchStateManager = contextualSearchStateManager;
         mRotationTouchHelper = rotationTouchHelper;
@@ -167,7 +157,7 @@
         // Register for exclusion updates
         lifeCycle.addCloseable(this::unregisterExclusionListener);
 
-        // Register for display changes changes
+        // Register for display changes
         mDisplayController.addChangeListener(this);
         onDisplayInfoChanged(context, mDisplayController.getInfo(), CHANGE_ALL);
         lifeCycle.addCloseable(() -> mDisplayController.removeChangeListener(this));
@@ -366,9 +356,8 @@
     /**
      * Updates the system ui state flags from SystemUI.
      */
-    public void setSystemUiStateFlags(@SystemUiStateFlags long stateFlags) {
+    public void setSystemUiFlags(@SystemUiStateFlags long stateFlags) {
         mSystemUiStateFlags = stateFlags;
-        mFlagChangeCallbacks.forEach(c -> c.accept(stateFlags));
     }
 
     /**
@@ -381,20 +370,6 @@
     }
 
     /**
-     * Adds a callback to receiver sysui flag changes
-     */
-    public void addSysUiFlagChangeCallback(LongConsumer callback) {
-        mFlagChangeCallbacks.add(callback);
-    }
-
-    /**
-     * Removes a previously added sysui flag change callback
-     */
-    public void removeSysUiFlagChangeCallback(LongConsumer callback) {
-        mFlagChangeCallbacks.remove(callback);
-    }
-
-    /**
      * Sets the flag that indicates whether a predictive back-to-home animation is in progress
      */
     public void setPredictiveBackToHomeInProgress(boolean isInProgress) {
@@ -409,22 +384,6 @@
     }
 
     /**
-     * Sets or clears a function to proxy swipe up transition behavior
-     */
-    public void setSwipeUpProxyProvider(
-            @Nullable Function<GestureState, AnimatedFloat> swipeUpProxyProvider) {
-        mSwipeUpProxyProvider = swipeUpProxyProvider;
-    }
-
-    /**
-     * Returns a proxy animation for swipe up transition if a proxy function was previously set
-     */
-    public AnimatedFloat getSwipeUpProxy(GestureState state) {
-        Function<GestureState, AnimatedFloat> provider = mSwipeUpProxyProvider;
-        return provider != null ? provider.apply(state) : null;
-    }
-
-    /**
      * @return whether SystemUI is in a state where we can start a system gesture.
      */
     public boolean canStartSystemGesture() {
@@ -577,8 +536,6 @@
      */
     public void setAssistantVisibility(float visibility) {
         mAssistantVisibility = visibility;
-        mAssistantVisibilityChangeListeners.forEach(
-                l -> l.onAssistantVisibilityChanged(visibility));
     }
 
     /**
@@ -588,16 +545,6 @@
         return mAssistantVisibility;
     }
 
-    /** Add a listener for assistant visibility changes */
-    public void addAssistantVisibilityChangeListener(AssistantVisibilityChangeListener l) {
-        mAssistantVisibilityChangeListeners.add(l);
-    }
-
-    /** Removes a previously added visibility change listener */
-    public void removeAssistantVisibilityChangeListener(AssistantVisibilityChangeListener l) {
-        mAssistantVisibilityChangeListeners.remove(l);
-    }
-
     /**
      * @return whether the Assistant gesture can be used in 3 button navigation mode.
      */
@@ -687,13 +634,6 @@
         return  QuickStepContract.getSystemUiStateString(mSystemUiStateFlags);
     }
 
-    /** Interface for listening assistant visibility change */
-    @FunctionalInterface
-    public interface AssistantVisibilityChangeListener {
-        /** Called when assistant visibility changes */
-        void onAssistantVisibilityChanged(float visibility);
-    }
-
     public void dump(PrintWriter pw) {
         pw.println("DeviceState:");
         pw.println("  canStartSystemGesture=" + canStartSystemGesture());
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index 1d83d42..f53a8b6 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -48,11 +48,13 @@
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.Executors.SimpleThreadFactory;
 import com.android.launcher3.util.LockedUserState;
+import com.android.launcher3.util.PerDisplayObjectProvider;
 import com.android.launcher3.util.SafeCloseable;
 import com.android.quickstep.dagger.QuickstepBaseAppComponent;
 import com.android.quickstep.recents.data.RecentTasksDataSource;
 import com.android.quickstep.recents.data.TaskVisualsChangeNotifier;
 import com.android.quickstep.util.DesktopTask;
+import com.android.quickstep.util.ExternalDisplaysKt;
 import com.android.quickstep.util.GroupTask;
 import com.android.quickstep.util.TaskVisualsChangeListener;
 import com.android.systemui.shared.recents.model.Task;
@@ -61,6 +63,8 @@
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.TaskStackChangeListeners;
 
+import dagger.Lazy;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
@@ -72,8 +76,6 @@
 
 import javax.inject.Inject;
 
-import dagger.Lazy;
-
 /**
  * Singleton class to load and manage recents model.
  */
@@ -101,15 +103,17 @@
      public RecentsModel(@ApplicationContext Context context,
             SystemUiProxy systemUiProxy,
             TopTaskTracker topTaskTracker,
-            DisplayController displayController,
             LockedUserState lockedUserState,
+            PerDisplayObjectProvider perDisplayObjectProvider,
             Lazy<ThemeManager> themeManagerLazy,
             DaggerSingletonTracker tracker
             ) {
         // Lazily inject the ThemeManager and access themeManager once the device is
         // unlocked. See b/393248495 for details.
         this(context, new IconProvider(context), systemUiProxy, topTaskTracker,
-                displayController, lockedUserState,themeManagerLazy, tracker);
+                perDisplayObjectProvider.getDisplayController(
+                        ExternalDisplaysKt.getValidDisplayId(context)),
+                lockedUserState, themeManagerLazy, tracker);
     }
 
     @SuppressLint("VisibleForTests")
diff --git a/quickstep/src/com/android/quickstep/RotationTouchHelper.java b/quickstep/src/com/android/quickstep/RotationTouchHelper.java
index a614327..387adfd 100644
--- a/quickstep/src/com/android/quickstep/RotationTouchHelper.java
+++ b/quickstep/src/com/android/quickstep/RotationTouchHelper.java
@@ -43,6 +43,7 @@
 import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
 import com.android.launcher3.util.DisplayController.Info;
 import com.android.launcher3.util.NavigationMode;
+import com.android.launcher3.util.PerDisplayObjectProvider;
 import com.android.quickstep.util.RecentsOrientedState;
 import com.android.systemui.shared.Flags;
 import com.android.systemui.shared.system.QuickStepContract;
@@ -136,11 +137,11 @@
 
     @Inject
     RotationTouchHelper(@ApplicationContext Context context,
-            DisplayController displayController,
+            PerDisplayObjectProvider displayControllerProvider,
             SystemUiProxy systemUiProxy,
             DaggerSingletonTracker lifeCycle) {
         mContext = context;
-        mDisplayController = displayController;
+        mDisplayController = displayControllerProvider.getDisplayController(DEFAULT_DISPLAY);
         mSystemUiProxy = systemUiProxy;
         mDisplayId = DEFAULT_DISPLAY;
 
diff --git a/quickstep/src/com/android/quickstep/SimpleOrientationTouchTransformer.java b/quickstep/src/com/android/quickstep/SimpleOrientationTouchTransformer.java
index d2a491d..9ebaffa 100644
--- a/quickstep/src/com/android/quickstep/SimpleOrientationTouchTransformer.java
+++ b/quickstep/src/com/android/quickstep/SimpleOrientationTouchTransformer.java
@@ -15,6 +15,8 @@
  */
 package com.android.quickstep;
 
+import static android.view.Display.DEFAULT_DISPLAY;
+
 import static com.android.launcher3.util.DisplayController.CHANGE_ACTIVE_SCREEN;
 import static com.android.launcher3.util.DisplayController.CHANGE_ALL;
 import static com.android.launcher3.util.DisplayController.CHANGE_ROTATION;
@@ -27,6 +29,7 @@
 import com.android.launcher3.util.DaggerSingletonObject;
 import com.android.launcher3.util.DaggerSingletonTracker;
 import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.PerDisplayObjectProvider;
 import com.android.quickstep.dagger.QuickstepBaseAppComponent;
 
 import javax.inject.Inject;
@@ -45,8 +48,10 @@
 
     @Inject
     public SimpleOrientationTouchTransformer(@ApplicationContext Context context,
-            DisplayController displayController,
+            PerDisplayObjectProvider displayControllerProvider,
             DaggerSingletonTracker tracker) {
+        DisplayController displayController = displayControllerProvider.getDisplayController(
+                DEFAULT_DISPLAY);
         displayController.addChangeListener(this);
         tracker.addCloseable(() -> displayController.removeChangeListener(this));
 
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.kt b/quickstep/src/com/android/quickstep/SystemUiProxy.kt
index 32bdcdb..1f3eb2a 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.kt
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.kt
@@ -65,6 +65,7 @@
 import com.android.systemui.shared.recents.ISystemUiProxy
 import com.android.systemui.shared.recents.model.ThumbnailData.Companion.wrap
 import com.android.systemui.shared.system.QuickStepContract
+import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags
 import com.android.systemui.shared.system.RecentsAnimationControllerCompat
 import com.android.systemui.shared.system.RecentsAnimationListener
 import com.android.systemui.shared.system.smartspace.ILauncherUnlockAnimationController
@@ -161,6 +162,9 @@
     private val asyncHandler =
         Handler(Executors.UI_HELPER_EXECUTOR.looper) { handleMessageAsync(it) }
 
+    // TODO(141886704): Find a way to remove this
+    @SystemUiStateFlags var lastSystemUiStateFlags: Long = 0
+
     /**
      * This is a singleton pending intent that is used to start recents via Shell (which is a
      * different process). It is bare-bones, so it's expected that the component and options will be
@@ -190,7 +194,7 @@
     private inline fun executeWithErrorLog(
         errorMsg: () -> String,
         tag: String = TAG,
-        callback: () -> Unit,
+        callback: () -> Any?,
     ) {
         try {
             callback.invoke()
@@ -228,30 +232,36 @@
      * Sets proxy state, including death linkage, various listeners, and other configuration objects
      */
     @MainThread
-    fun setProxy(bundle: Bundle) {
+    fun setProxy(
+        proxy: ISystemUiProxy?,
+        pip: IPip?,
+        bubbles: IBubbles?,
+        splitScreen: ISplitScreen?,
+        oneHanded: IOneHanded?,
+        shellTransitions: IShellTransitions?,
+        startingWindow: IStartingWindow?,
+        recentTasks: IRecentTasks?,
+        sysuiUnlockAnimationController: ISysuiUnlockAnimationController?,
+        backAnimation: IBackAnimation?,
+        desktopMode: IDesktopMode?,
+        unfoldAnimation: IUnfoldAnimation?,
+        dragAndDrop: IDragAndDrop?,
+    ) {
         Preconditions.assertUIThread()
         unlinkToDeath()
-        systemUiProxy = ISystemUiProxy.Stub.asInterface(bundle.getBinder(ISystemUiProxy.DESCRIPTOR))
-        pip = IPip.Stub.asInterface(bundle.getBinder(IPip.DESCRIPTOR))
-        bubbles = IBubbles.Stub.asInterface(bundle.getBinder(IBubbles.DESCRIPTOR))
-        splitScreen = ISplitScreen.Stub.asInterface(bundle.getBinder(ISplitScreen.DESCRIPTOR))
-        oneHanded = IOneHanded.Stub.asInterface(bundle.getBinder(IOneHanded.DESCRIPTOR))
-        shellTransitions =
-            IShellTransitions.Stub.asInterface(bundle.getBinder(IShellTransitions.DESCRIPTOR))
-        startingWindow =
-            IStartingWindow.Stub.asInterface(bundle.getBinder(IStartingWindow.DESCRIPTOR))
-        sysuiUnlockAnimationController =
-            ISysuiUnlockAnimationController.Stub.asInterface(
-                bundle.getBinder(ISysuiUnlockAnimationController.DESCRIPTOR)
-            )
-        recentTasks = IRecentTasks.Stub.asInterface(bundle.getBinder(IRecentTasks.DESCRIPTOR))
-        backAnimation = IBackAnimation.Stub.asInterface(bundle.getBinder(IBackAnimation.DESCRIPTOR))
-        desktopMode = IDesktopMode.Stub.asInterface(bundle.getBinder(IDesktopMode.DESCRIPTOR))
-        unfoldAnimation =
-            if (Flags.enableUnfoldStateAnimation()) null
-            else IUnfoldAnimation.Stub.asInterface(bundle.getBinder(IUnfoldAnimation.DESCRIPTOR))
-        dragAndDrop = IDragAndDrop.Stub.asInterface(bundle.getBinder(IDragAndDrop.DESCRIPTOR))
-
+        systemUiProxy = proxy
+        this.pip = pip
+        this.bubbles = bubbles
+        this.splitScreen = splitScreen
+        this.oneHanded = oneHanded
+        this.shellTransitions = shellTransitions
+        this.startingWindow = startingWindow
+        this.sysuiUnlockAnimationController = sysuiUnlockAnimationController
+        this.recentTasks = recentTasks
+        this.backAnimation = backAnimation
+        this.desktopMode = desktopMode
+        this.unfoldAnimation = if (Flags.enableUnfoldStateAnimation()) null else unfoldAnimation
+        this.dragAndDrop = dragAndDrop
         linkToDeath()
         // re-attach the listeners once missing due to setProxy has not been initialized yet.
         setPipAnimationListener(pipAnimationListener)
@@ -279,12 +289,15 @@
         stateChangeCallbacks.forEach { it.run() }
 
         if (unfoldTransitionProvider != null) {
-            unfoldTransitionProvider.isActive = false
-            executeWithErrorLog({ "Failed to set unfold anim listener" }) {
-                unfoldAnimation?.apply {
-                    setListener(unfoldTransitionProvider)
+            if (unfoldAnimation != null) {
+                try {
+                    unfoldAnimation.setListener(unfoldTransitionProvider)
                     unfoldTransitionProvider.isActive = true
+                } catch (e: RemoteException) {
+                    // Ignore
                 }
+            } else {
+                unfoldTransitionProvider.isActive = false
             }
         }
     }
@@ -292,7 +305,9 @@
     /**
      * Clear the proxy to release held resources and turn the majority of its operations into no-ops
      */
-    @MainThread fun clearProxy() = setProxy(Bundle.EMPTY)
+    @MainThread
+    fun clearProxy() =
+        setProxy(null, null, null, null, null, null, null, null, null, null, null, null, null)
 
     /** Adds a callback to be notified whenever the active state changes */
     fun addOnStateChangeListener(callback: Runnable) = stateChangeCallbacks.add(callback)
@@ -1203,8 +1218,12 @@
         override fun onAnimationCanceled(taskIds: IntArray?, taskSnapshots: Array<TaskSnapshot>?) =
             listener.onAnimationCanceled(wrap(taskIds, taskSnapshots))
 
-        override fun onTasksAppeared(apps: Array<RemoteAnimationTarget>?) =
-            listener.onTasksAppeared(apps)
+        override fun onTasksAppeared(
+            apps: Array<RemoteAnimationTarget>?,
+            transitionInfo: TransitionInfo?,
+        ) {
+            listener.onTasksAppeared(apps, transitionInfo)
+        }
     }
 
     //
diff --git a/quickstep/src/com/android/quickstep/TISBinder.kt b/quickstep/src/com/android/quickstep/TISBinder.kt
deleted file mode 100644
index 683012c..0000000
--- a/quickstep/src/com/android/quickstep/TISBinder.kt
+++ /dev/null
@@ -1,249 +0,0 @@
-/*
- * Copyright (C) 2025 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep
-
-import android.graphics.Region
-import android.os.Bundle
-import android.os.IRemoteCallback
-import android.os.RemoteException
-import android.util.Log
-import androidx.annotation.BinderThread
-import androidx.annotation.VisibleForTesting
-import com.android.launcher3.taskbar.TaskbarManager
-import com.android.launcher3.testing.TestLogging
-import com.android.launcher3.testing.shared.TestProtocol
-import com.android.launcher3.util.Executors
-import com.android.quickstep.OverviewCommandHelper.CommandType.HIDE
-import com.android.quickstep.OverviewCommandHelper.CommandType.KEYBOARD_INPUT
-import com.android.quickstep.OverviewCommandHelper.CommandType.SHOW
-import com.android.quickstep.OverviewCommandHelper.CommandType.TOGGLE
-import com.android.quickstep.util.ActivityPreloadUtil.preloadOverviewForTIS
-import com.android.quickstep.util.ContextualSearchInvoker
-import com.android.systemui.shared.recents.ILauncherProxy
-import com.android.systemui.shared.statusbar.phone.BarTransitions.TransitionMode
-import com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS
-import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags
-import java.lang.ref.WeakReference
-
-/** Local ILauncherProxy implementation with some methods for local components */
-private const val TAG = "TISBinder"
-
-class TISBinder internal constructor(tis: TouchInteractionService) : ILauncherProxy.Stub() {
-
-    private val mTis = WeakReference(tis)
-
-    /** Returns the [TaskbarManager] or `null` if TouchInteractionService is not connected */
-    val taskbarManager: TaskbarManager?
-        get() = mTis.get()?.taskbarManager
-
-    /** Returns the [OverviewCommandHelper] or `null` if TouchInteractionService is not connected */
-    val overviewCommandHelper: OverviewCommandHelper?
-        get() = mTis.get()?.overviewCommandHelper
-
-    private val deviceState: RecentsAnimationDeviceState?
-        get() = mTis.get()?.let { RecentsAnimationDeviceState.INSTANCE[it] }
-
-    private inline fun executeForTaskbarManagerOnMain(
-        crossinline callback: TaskbarManager.() -> Unit
-    ) {
-        Executors.MAIN_EXECUTOR.execute { taskbarManager?.let { callback.invoke(it) } }
-    }
-
-    @BinderThread
-    override fun onInitialize(bundle: Bundle) {
-        Executors.MAIN_EXECUTOR.execute {
-            mTis.get()?.let {
-                SystemUiProxy.INSTANCE[it].setProxy(bundle)
-                preloadOverviewForTIS(it, true /* fromInit */)
-            }
-        }
-    }
-
-    @BinderThread
-    override fun onTaskbarToggled() {
-        executeForTaskbarManagerOnMain { currentActivityContext?.toggleTaskbarStash() }
-    }
-
-    @BinderThread
-    override fun onOverviewToggle() {
-        TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onOverviewToggle")
-        mTis.get()?.let { tis ->
-            // If currently screen pinning, do not enter overview
-            if (RecentsAnimationDeviceState.INSTANCE[tis].isScreenPinningActive) {
-                return@let
-            }
-            TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS)
-            tis.overviewCommandHelper.addCommand(TOGGLE)
-        }
-    }
-
-    @BinderThread
-    override fun onOverviewShown(triggeredFromAltTab: Boolean) {
-        overviewCommandHelper?.apply {
-            if (triggeredFromAltTab) {
-                TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS)
-                addCommand(KEYBOARD_INPUT)
-            } else {
-                addCommand(SHOW)
-            }
-        }
-    }
-
-    @BinderThread
-    override fun onOverviewHidden(triggeredFromAltTab: Boolean, triggeredFromHomeKey: Boolean) {
-        overviewCommandHelper?.apply {
-            if (triggeredFromAltTab && !triggeredFromHomeKey) {
-                // onOverviewShownFromAltTab hides the overview and ends at the target app
-                addCommand(HIDE)
-            }
-        }
-    }
-
-    @BinderThread
-    override fun onAssistantAvailable(available: Boolean, longPressHomeEnabled: Boolean) {
-        Executors.MAIN_EXECUTOR.execute {
-            deviceState?.setAssistantAvailable(available)
-            taskbarManager?.onLongPressHomeEnabled(longPressHomeEnabled)
-        }
-    }
-
-    @BinderThread
-    override fun onAssistantVisibilityChanged(visibility: Float) {
-        Executors.MAIN_EXECUTOR.execute { deviceState?.assistantVisibility = visibility }
-    }
-
-    /**
-     * Sent when the assistant has been invoked with the given type (defined in AssistManager) and
-     * should be shown. This method is used if SystemUiProxy#setAssistantOverridesRequested was
-     * previously called including this invocation type.
-     */
-    override fun onAssistantOverrideInvoked(invocationType: Int) {
-        mTis.get()?.let { tis ->
-            if (!ContextualSearchInvoker(tis).tryStartAssistOverride(invocationType)) {
-                Log.w(TAG, "Failed to invoke Assist override")
-            }
-        }
-    }
-
-    @BinderThread
-    override fun onSystemUiStateChanged(@SystemUiStateFlags stateFlags: Long) {
-        Executors.MAIN_EXECUTOR.execute { deviceState?.systemUiStateFlags = stateFlags }
-    }
-
-    @BinderThread
-    override fun onActiveNavBarRegionChanges(region: Region) {
-        Executors.MAIN_EXECUTOR.execute { deviceState?.setDeferredGestureRegion(region) }
-    }
-
-    @BinderThread
-    override fun enterStageSplitFromRunningApp(leftOrTop: Boolean) {
-        mTis.get()?.let { tis ->
-            OverviewComponentObserver.INSTANCE[tis]
-                .containerInterface
-                .createdContainer
-                ?.enterStageSplitFromRunningApp(leftOrTop)
-        }
-    }
-
-    @BinderThread
-    override fun onDisplayAddSystemDecorations(displayId: Int) {
-        executeForTaskbarManagerOnMain { onDisplayAddSystemDecorations(displayId) }
-    }
-
-    @BinderThread
-    override fun onDisplayRemoved(displayId: Int) {
-        executeForTaskbarManagerOnMain { onDisplayRemoved(displayId) }
-    }
-
-    @BinderThread
-    override fun onDisplayRemoveSystemDecorations(displayId: Int) {
-        executeForTaskbarManagerOnMain { onDisplayRemoveSystemDecorations(displayId) }
-    }
-
-    @BinderThread
-    override fun updateWallpaperVisibility(displayId: Int, visible: Boolean) {
-        executeForTaskbarManagerOnMain { setWallpaperVisible(displayId, visible) }
-    }
-
-    @BinderThread
-    override fun checkNavBarModes(displayId: Int) {
-        executeForTaskbarManagerOnMain { checkNavBarModes(displayId) }
-    }
-
-    @BinderThread
-    override fun finishBarAnimations(displayId: Int) {
-        executeForTaskbarManagerOnMain { finishBarAnimations(displayId) }
-    }
-
-    @BinderThread
-    override fun touchAutoDim(displayId: Int, reset: Boolean) {
-        executeForTaskbarManagerOnMain { touchAutoDim(displayId, reset) }
-    }
-
-    @BinderThread
-    override fun transitionTo(displayId: Int, @TransitionMode barMode: Int, animate: Boolean) {
-        executeForTaskbarManagerOnMain { transitionTo(displayId, barMode, animate) }
-    }
-
-    @BinderThread
-    override fun appTransitionPending(pending: Boolean) {
-        executeForTaskbarManagerOnMain { appTransitionPending(pending) }
-    }
-
-    override fun onRotationProposal(rotation: Int, isValid: Boolean) {
-        executeForTaskbarManagerOnMain { onRotationProposal(rotation, isValid) }
-    }
-
-    override fun disable(displayId: Int, state1: Int, state2: Int, animate: Boolean) {
-        executeForTaskbarManagerOnMain { disableNavBarElements(displayId, state1, state2, animate) }
-    }
-
-    override fun onSystemBarAttributesChanged(displayId: Int, behavior: Int) {
-        executeForTaskbarManagerOnMain { onSystemBarAttributesChanged(displayId, behavior) }
-    }
-
-    override fun onTransitionModeUpdated(barMode: Int, checkBarModes: Boolean) {
-        executeForTaskbarManagerOnMain { onTransitionModeUpdated(barMode, checkBarModes) }
-    }
-
-    override fun onNavButtonsDarkIntensityChanged(darkIntensity: Float) {
-        executeForTaskbarManagerOnMain { onNavButtonsDarkIntensityChanged(darkIntensity) }
-    }
-
-    override fun onNavigationBarLumaSamplingEnabled(displayId: Int, enable: Boolean) {
-        executeForTaskbarManagerOnMain { onNavigationBarLumaSamplingEnabled(displayId, enable) }
-    }
-
-    override fun onUnbind(reply: IRemoteCallback) {
-        // Run everything in the same main thread block to ensure the cleanup happens before
-        // sending the reply.
-        Executors.MAIN_EXECUTOR.execute {
-            taskbarManager?.destroy()
-            try {
-                reply.sendResult(null)
-            } catch (e: RemoteException) {
-                Log.w(TAG, "onUnbind: Failed to reply to LauncherProxyService", e)
-            }
-        }
-    }
-
-    @VisibleForTesting
-    fun injectFakeTrackpadForTesting() = mTis.get()?.injectFakeTrackpadForTesting()
-
-    @VisibleForTesting fun ejectFakeTrackpadForTesting() = mTis.get()?.ejectFakeTrackpadForTesting()
-
-    @VisibleForTesting fun refreshOverviewTarget() = mTis.get()?.refreshOverviewTarget()
-}
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index e449048..9810308 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -54,25 +54,22 @@
 import com.android.quickstep.views.RecentsView;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.QuickStepContract;
-import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.TaskStackChangeListeners;
 
 import java.io.PrintWriter;
 import java.util.HashMap;
-import java.util.function.LongConsumer;
 
 public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAnimationListener {
     public static final boolean SHELL_TRANSITIONS_ROTATION =
             SystemProperties.getBoolean("persist.wm.debug.shell_transit_rotate", false);
 
     private final Context mCtx;
-    private final RecentsAnimationDeviceState mDeviceState;
-    private final SystemUiProxy mSystemUiProxy;
     private RecentsAnimationController mController;
     private RecentsAnimationCallbacks mCallbacks;
     private RecentsAnimationTargets mTargets;
     private TransitionInfo mTransitionInfo;
+    private RecentsAnimationDeviceState mDeviceState;
 
     // Temporary until we can hook into gesture state events
     private GestureState mLastGestureState;
@@ -105,22 +102,12 @@
         }
     };
 
-
-    private @SystemUiStateFlags long mLastSysuiFlags;
-    private final LongConsumer mSysUiFlagChangeCallback = this::onSystemUiFlagsChanged;
-
     TaskAnimationManager(Context ctx, RecentsAnimationDeviceState deviceState) {
-        this(ctx, deviceState, SystemUiProxy.INSTANCE.get(ctx));
-    }
-
-    TaskAnimationManager(Context ctx, RecentsAnimationDeviceState deviceState,
-            SystemUiProxy systemUiProxy) {
         mCtx = ctx;
         mDeviceState = deviceState;
-        mSystemUiProxy = systemUiProxy;
-        mLastSysuiFlags = QuickStepContract.SYSUI_STATE_AWAKE;
-        mDeviceState.addSysUiFlagChangeCallback(mSysUiFlagChangeCallback);
-        onSystemUiFlagsChanged(mDeviceState.getSystemUiStateFlags());
+    }
+    SystemUiProxy getSystemUiProxy() {
+        return SystemUiProxy.INSTANCE.get(mCtx);
     }
 
     boolean shouldIgnoreMotionEvents() {
@@ -164,7 +151,7 @@
 
         final BaseContainerInterface containerInterface = gestureState.getContainerInterface();
         mLastGestureState = gestureState;
-        RecentsAnimationCallbacks newCallbacks = new RecentsAnimationCallbacks(mSystemUiProxy);
+        RecentsAnimationCallbacks newCallbacks = new RecentsAnimationCallbacks(getSystemUiProxy());
         mCallbacks = newCallbacks;
         mCallbacks.addListener(new RecentsAnimationCallbacks.RecentsAnimationListener() {
             @Override
@@ -244,7 +231,8 @@
             }
 
             @Override
-            public void onTasksAppeared(RemoteAnimationTarget[] appearedTaskTargets) {
+            public void onTasksAppeared(RemoteAnimationTarget[] appearedTaskTargets,
+                    @Nullable TransitionInfo transitionInfo) {
                 RemoteAnimationTarget appearedTaskTarget = appearedTaskTargets[0];
                 BaseContainerInterface containerInterface =
                         mLastGestureState.getContainerInterface();
@@ -277,7 +265,8 @@
                         recentsView.launchSideTaskInLiveTileMode(appearedTaskTarget.taskId,
                                 appearedTaskTargets,
                                 new RemoteAnimationTarget[0] /* wallpaper */,
-                                nonAppTargets /* nonApps */);
+                                nonAppTargets /* nonApps */,
+                                transitionInfo);
                         return;
                     } else {
                         ActiveGestureProtoLogProxy.logLaunchingSideTaskFailed();
@@ -302,7 +291,7 @@
         if(containerInterface.getCreatedContainer() instanceof RecentsWindowManager
                 && (Flags.enableFallbackOverviewInWindow()
                         || Flags.enableLauncherOverviewInWindow())) {
-            mRecentsAnimationStartPending = mSystemUiProxy.startRecentsActivity(intent, options,
+            mRecentsAnimationStartPending = getSystemUiProxy().startRecentsActivity(intent, options,
                     mCallbacks, gestureState.useSyntheticRecentsTransition());
             RecentsDisplayModel.getINSTANCE().get(mCtx)
                     .getRecentsWindowManager(mDeviceState.getDisplayId())
@@ -334,7 +323,7 @@
                 });
             }
 
-            mRecentsAnimationStartPending = mSystemUiProxy.startRecentsActivity(intent,
+            mRecentsAnimationStartPending = getSystemUiProxy().startRecentsActivity(intent,
                     options, mCallbacks, false /* useSyntheticRecentsTransition */);
         }
 
@@ -360,13 +349,8 @@
         return mCallbacks;
     }
 
-    private void onSystemUiFlagsChanged(@SystemUiStateFlags long newSysUIFlags) {
-        onSystemUiFlagsChanged(mLastSysuiFlags, newSysUIFlags);
-        mLastSysuiFlags = newSysUIFlags;
-    }
-
-    private void onSystemUiFlagsChanged(@SystemUiStateFlags long lastSysUIFlags,
-            @SystemUiStateFlags long newSysUIFlags) {
+    public void onSystemUiFlagsChanged(@QuickStepContract.SystemUiStateFlags long lastSysUIFlags,
+            @QuickStepContract.SystemUiStateFlags long newSysUIFlags) {
         long isShadeExpandedFlagMask =
                 SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED | SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
         boolean wasExpanded = hasAnyFlag(lastSysUIFlags, isShadeExpandedFlagMask);
@@ -527,8 +511,4 @@
             mLastGestureState.dump(prefix + '\t', pw);
         }
     }
-
-    public void onDestroy() {
-        mDeviceState.removeSysUiFlagChangeCallback(mSysUiFlagChangeCallback);
-    }
 }
diff --git a/quickstep/src/com/android/quickstep/TaskIconCache.kt b/quickstep/src/com/android/quickstep/TaskIconCache.kt
index 6a7f1af..3fbf59a 100644
--- a/quickstep/src/com/android/quickstep/TaskIconCache.kt
+++ b/quickstep/src/com/android/quickstep/TaskIconCache.kt
@@ -153,7 +153,7 @@
     private fun createIconFactory() =
         BaseIconFactory(
             context,
-            DisplayController.INSTANCE.get(context).info.densityDpi,
+            DisplayController.get(context).info.densityDpi,
             context.resources.getDimensionPixelSize(R.dimen.task_icon_cache_default_icon_size),
         )
 
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index e47223b..ff05743 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -169,6 +169,7 @@
             @NonNull RemoteAnimationTarget[] wallpaperTargets,
             @NonNull RemoteAnimationTarget[] nonAppTargets,
             @Nullable DepthController depthController,
+            @Nullable TransitionInfo transitionInfo,
             PendingAnimation out) {
         boolean isQuickSwitch = v.isEndQuickSwitchCuj();
         v.setEndQuickSwitchCuj(false);
@@ -191,8 +192,7 @@
             RemoteTargetGluer gluer = new RemoteTargetGluer(v.getContext(),
                     recentsView.getSizeStrategy(), targets, forDesktop);
             if (forDesktop) {
-                remoteTargetHandles =
-                        gluer.assignTargetsForDesktop(targets, /* transitionInfo=*/ null);
+                remoteTargetHandles = gluer.assignTargetsForDesktop(targets, transitionInfo);
             } else if (v.containsMultipleTasks()) {
                 remoteTargetHandles = gluer.assignTargetsForSplitScreen(targets,
                         ((GroupedTaskView) v).getSplitBoundsConfig());
@@ -230,7 +230,7 @@
 
                 // RecentsView never updates the display rotation until swipe-up so the value may
                 // be stale. Use the display value instead.
-                int displayRotation = DisplayController.INSTANCE.get(context).getInfo().rotation;
+                int displayRotation = DisplayController.get(context).getInfo().rotation;
                 tvsLocal.getOrientationState().update(displayRotation, displayRotation);
 
                 tvsLocal.fullScreenProgress.value = 0;
@@ -462,7 +462,7 @@
         final RecentsView recentsView = launchingTaskView.getRecentsView();
         composeRecentsLaunchAnimator(animatorSet, launchingTaskView, appTargets, wallpaperTargets,
                 nonAppTargets, /* launcherClosing */ true, stateManager, recentsView,
-                depthController);
+                depthController, /* transitionInfo= */ null);
 
         t.apply();
         animatorSet.start();
@@ -501,7 +501,7 @@
             composeRecentsLaunchAnimator(animatorSet, launchingTaskView,
                     appTargets, wallpaperTargets, nonAppTargets,
                     true, stateManager,
-                    recentsView, depthController);
+                    recentsView, depthController, /* transitionInfo= */ null);
             animatorSet.start();
             return;
         }
@@ -593,7 +593,7 @@
 
         composeRecentsLaunchAnimator(animatorSet, launchingTaskView, apps, wallpaper, nonApps,
                 true /* launcherClosing */, stateManager, launchingTaskView.getRecentsView(),
-                depthController);
+                depthController, transitionInfo);
 
         return animatorSet;
     }
@@ -603,13 +603,13 @@
             @NonNull RemoteAnimationTarget[] wallpaperTargets,
             @NonNull RemoteAnimationTarget[] nonAppTargets, boolean launcherClosing,
             @NonNull StateManager stateManager, @NonNull RecentsView recentsView,
-            @Nullable DepthController depthController) {
+            @Nullable DepthController depthController, @Nullable TransitionInfo transitionInfo) {
         boolean skipLauncherChanges = !launcherClosing;
 
         TaskView taskView = findTaskViewToLaunch(recentsView, v, appTargets);
         PendingAnimation pa = new PendingAnimation(RECENTS_LAUNCH_DURATION);
         createRecentsWindowAnimator(recentsView, taskView, skipLauncherChanges, appTargets,
-                wallpaperTargets, nonAppTargets, depthController, pa);
+                wallpaperTargets, nonAppTargets, depthController, transitionInfo, pa);
         if (launcherClosing) {
             // TODO(b/182592057): differentiate between "restore split" vs "launch fullscreen app"
             TaskViewUtils.createSplitAuxiliarySurfacesAnimator(nonAppTargets, true /*shown*/,
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 22fc2ea..7a4c06b 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -36,6 +36,7 @@
 import static com.android.quickstep.InputConsumer.TYPE_CURSOR_HOVER;
 import static com.android.quickstep.InputConsumerUtils.newConsumer;
 import static com.android.quickstep.InputConsumerUtils.tryCreateAssistantInputConsumer;
+import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 
 import android.app.PendingIntent;
 import android.app.Service;
@@ -43,9 +44,12 @@
 import android.content.IIntentSender;
 import android.content.Intent;
 import android.content.res.Configuration;
+import android.graphics.Region;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.IRemoteCallback;
 import android.os.Looper;
+import android.os.RemoteException;
 import android.os.SystemClock;
 import android.util.Log;
 import android.view.Choreographer;
@@ -53,6 +57,7 @@
 import android.view.InputEvent;
 import android.view.MotionEvent;
 
+import androidx.annotation.BinderThread;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
@@ -62,6 +67,7 @@
 import com.android.launcher3.EncryptionType;
 import com.android.launcher3.Flags;
 import com.android.launcher3.LauncherPrefs;
+import com.android.launcher3.anim.AnimatedFloat;
 import com.android.launcher3.desktop.DesktopAppLaunchTransitionManager;
 import com.android.launcher3.statehandlers.DesktopVisibilityController;
 import com.android.launcher3.statemanager.StatefulActivity;
@@ -92,14 +98,34 @@
 import com.android.quickstep.util.ActiveGestureProtoLogProxy;
 import com.android.quickstep.util.ActiveTrackpadList;
 import com.android.quickstep.util.ActivityPreloadUtil;
+import com.android.quickstep.util.ContextualSearchInvoker;
 import com.android.quickstep.util.ContextualSearchStateManager;
 import com.android.quickstep.views.RecentsViewContainer;
+import com.android.systemui.shared.recents.ILauncherProxy;
+import com.android.systemui.shared.recents.ISystemUiProxy;
+import com.android.systemui.shared.statusbar.phone.BarTransitions;
 import com.android.systemui.shared.system.InputChannelCompat.InputEventReceiver;
 import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.shared.system.InputMonitorCompat;
+import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
+import com.android.systemui.shared.system.smartspace.ISysuiUnlockAnimationController;
+import com.android.systemui.unfold.progress.IUnfoldAnimation;
+import com.android.wm.shell.back.IBackAnimation;
+import com.android.wm.shell.bubbles.IBubbles;
+import com.android.wm.shell.common.pip.IPip;
+import com.android.wm.shell.desktopmode.IDesktopMode;
+import com.android.wm.shell.draganddrop.IDragAndDrop;
+import com.android.wm.shell.onehanded.IOneHanded;
+import com.android.wm.shell.recents.IRecentTasks;
+import com.android.wm.shell.shared.IShellTransitions;
+import com.android.wm.shell.splitscreen.ISplitScreen;
+import com.android.wm.shell.startingsurface.IStartingWindow;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
+import java.util.function.Consumer;
+import java.util.function.Function;
 
 /**
  * Service connected by system-UI for handling touch interaction.
@@ -115,6 +141,371 @@
 
     private final TISBinder mTISBinder = new TISBinder(this);
 
+    /**
+     * Local ILauncherProxy implementation with some methods for local components
+     */
+    public static class TISBinder extends ILauncherProxy.Stub {
+
+        private final WeakReference<TouchInteractionService> mTis;
+
+        private TISBinder(TouchInteractionService tis) {
+            mTis = new WeakReference<>(tis);
+        }
+
+        @BinderThread
+        public void onInitialize(Bundle bundle) {
+            ISystemUiProxy proxy = ISystemUiProxy.Stub.asInterface(
+                    bundle.getBinder(ISystemUiProxy.DESCRIPTOR));
+            IPip pip = IPip.Stub.asInterface(bundle.getBinder(IPip.DESCRIPTOR));
+            IBubbles bubbles = IBubbles.Stub.asInterface(bundle.getBinder(IBubbles.DESCRIPTOR));
+            ISplitScreen splitscreen = ISplitScreen.Stub.asInterface(bundle.getBinder(
+                    ISplitScreen.DESCRIPTOR));
+            IOneHanded onehanded = IOneHanded.Stub.asInterface(
+                    bundle.getBinder(IOneHanded.DESCRIPTOR));
+            IShellTransitions shellTransitions = IShellTransitions.Stub.asInterface(
+                    bundle.getBinder(IShellTransitions.DESCRIPTOR));
+            IStartingWindow startingWindow = IStartingWindow.Stub.asInterface(
+                    bundle.getBinder(IStartingWindow.DESCRIPTOR));
+            ISysuiUnlockAnimationController launcherUnlockAnimationController =
+                    ISysuiUnlockAnimationController.Stub.asInterface(
+                            bundle.getBinder(ISysuiUnlockAnimationController.DESCRIPTOR));
+            IRecentTasks recentTasks = IRecentTasks.Stub.asInterface(
+                    bundle.getBinder(IRecentTasks.DESCRIPTOR));
+            IBackAnimation backAnimation = IBackAnimation.Stub.asInterface(
+                    bundle.getBinder(IBackAnimation.DESCRIPTOR));
+            IDesktopMode desktopMode = IDesktopMode.Stub.asInterface(
+                    bundle.getBinder(IDesktopMode.DESCRIPTOR));
+            IUnfoldAnimation unfoldTransition = IUnfoldAnimation.Stub.asInterface(
+                    bundle.getBinder(IUnfoldAnimation.DESCRIPTOR));
+            IDragAndDrop dragAndDrop = IDragAndDrop.Stub.asInterface(
+                    bundle.getBinder(IDragAndDrop.DESCRIPTOR));
+            MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(tis -> {
+                SystemUiProxy.INSTANCE.get(tis).setProxy(proxy, pip,
+                        bubbles, splitscreen, onehanded, shellTransitions, startingWindow,
+                        recentTasks, launcherUnlockAnimationController, backAnimation, desktopMode,
+                        unfoldTransition, dragAndDrop);
+                tis.initInputMonitor("TISBinder#onInitialize()");
+                ActivityPreloadUtil.preloadOverviewForTIS(tis, true /* fromInit */);
+            }));
+        }
+
+        @BinderThread
+        @Override
+        public void onTaskbarToggled() {
+            MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(tis -> {
+                TaskbarActivityContext activityContext =
+                        tis.mTaskbarManager.getCurrentActivityContext();
+
+                if (activityContext != null) {
+                    activityContext.toggleTaskbarStash();
+                }
+            }));
+        }
+
+        @BinderThread
+        public void onOverviewToggle() {
+            TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onOverviewToggle");
+            executeForTouchInteractionService(tis -> {
+                // If currently screen pinning, do not enter overview
+                if (tis.mDeviceState.isScreenPinningActive()) {
+                    return;
+                }
+                TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
+                tis.mOverviewCommandHelper.addCommand(CommandType.TOGGLE);
+            });
+        }
+
+        @BinderThread
+        @Override
+        public void onOverviewShown(boolean triggeredFromAltTab) {
+            executeForTouchInteractionService(tis -> {
+                if (triggeredFromAltTab) {
+                    TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
+                    tis.mOverviewCommandHelper.addCommand(CommandType.KEYBOARD_INPUT);
+                } else {
+                    tis.mOverviewCommandHelper.addCommand(CommandType.SHOW);
+                }
+            });
+        }
+
+        @BinderThread
+        @Override
+        public void onOverviewHidden(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
+            executeForTouchInteractionService(tis -> {
+                if (triggeredFromAltTab && !triggeredFromHomeKey) {
+                    // onOverviewShownFromAltTab hides the overview and ends at the target app
+                    tis.mOverviewCommandHelper.addCommand(CommandType.HIDE);
+                }
+            });
+        }
+
+        @BinderThread
+        @Override
+        public void onAssistantAvailable(boolean available, boolean longPressHomeEnabled) {
+            MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(tis -> {
+                tis.mDeviceState.setAssistantAvailable(available);
+                tis.onAssistantVisibilityChanged();
+                executeForTaskbarManager(taskbarManager -> taskbarManager
+                        .onLongPressHomeEnabled(longPressHomeEnabled));
+            }));
+        }
+
+        @BinderThread
+        @Override
+        public void onAssistantVisibilityChanged(float visibility) {
+            MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(tis -> {
+                tis.mDeviceState.setAssistantVisibility(visibility);
+                tis.onAssistantVisibilityChanged();
+            }));
+        }
+
+        /**
+         * Sent when the assistant has been invoked with the given type (defined in AssistManager)
+         * and should be shown. This method is used if SystemUiProxy#setAssistantOverridesRequested
+         * was previously called including this invocation type.
+         */
+        @Override
+        public void onAssistantOverrideInvoked(int invocationType) {
+            executeForTouchInteractionService(tis -> {
+                if (!new ContextualSearchInvoker(tis).tryStartAssistOverride(invocationType)) {
+                    Log.w(TAG, "Failed to invoke Assist override");
+                }
+            });
+        }
+
+        @BinderThread
+        public void onSystemUiStateChanged(@SystemUiStateFlags long stateFlags) {
+            MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(tis -> {
+                long lastFlags = tis.mDeviceState.getSystemUiStateFlags();
+                tis.mDeviceState.setSystemUiFlags(stateFlags);
+                tis.onSystemUiFlagsChanged(lastFlags);
+            }));
+        }
+
+        @BinderThread
+        public void onActiveNavBarRegionChanges(Region region) {
+            MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(
+                    tis -> tis.mDeviceState.setDeferredGestureRegion(region)));
+        }
+
+        @BinderThread
+        @Override
+        public void enterStageSplitFromRunningApp(boolean leftOrTop) {
+            executeForTouchInteractionService(tis -> {
+                RecentsViewContainer container = tis.mOverviewComponentObserver
+                        .getContainerInterface().getCreatedContainer();
+                if (container != null) {
+                    container.enterStageSplitFromRunningApp(leftOrTop);
+                }
+            });
+        }
+
+        @BinderThread
+        @Override
+        public void onDisplayAddSystemDecorations(int displayId) {
+            executeForTaskbarManager(taskbarManager ->
+                            taskbarManager.onDisplayAddSystemDecorations(displayId));
+        }
+
+        @BinderThread
+        @Override
+        public void onDisplayRemoved(int displayId) {
+            executeForTaskbarManager(taskbarManager ->
+                    taskbarManager.onDisplayRemoved(displayId));
+        }
+
+        @BinderThread
+        @Override
+        public void onDisplayRemoveSystemDecorations(int displayId) {
+            executeForTaskbarManager(taskbarManager ->
+                    taskbarManager.onDisplayRemoveSystemDecorations(displayId));
+        }
+
+        @BinderThread
+        @Override
+        public void updateWallpaperVisibility(int displayId, boolean visible) {
+            executeForTaskbarManager(taskbarManager ->
+                    taskbarManager.setWallpaperVisible(displayId, visible));
+        }
+
+        @BinderThread
+        @Override
+        public void checkNavBarModes(int displayId) {
+            executeForTaskbarManager(taskbarManager ->
+                    taskbarManager.checkNavBarModes(displayId));
+        }
+
+        @BinderThread
+        @Override
+        public void finishBarAnimations(int displayId) {
+            executeForTaskbarManager(taskbarManager ->
+                    taskbarManager.finishBarAnimations(displayId));
+        }
+
+        @BinderThread
+        @Override
+        public void touchAutoDim(int displayId, boolean reset) {
+            executeForTaskbarManager(taskbarManager ->
+                    taskbarManager.touchAutoDim(displayId, reset));
+        }
+
+        @BinderThread
+        @Override
+        public void transitionTo(int displayId, @BarTransitions.TransitionMode int barMode,
+                boolean animate) {
+            executeForTaskbarManager(taskbarManager ->
+                    taskbarManager.transitionTo(displayId, barMode, animate));
+        }
+
+        @BinderThread
+        @Override
+        public void appTransitionPending(boolean pending) {
+            executeForTaskbarManager(taskbarManager ->
+                    taskbarManager.appTransitionPending(pending));
+        }
+
+        @Override
+        public void onRotationProposal(int rotation, boolean isValid) {
+            executeForTaskbarManager(taskbarManager ->
+                    taskbarManager.onRotationProposal(rotation, isValid));
+        }
+
+        @Override
+        public void disable(int displayId, int state1, int state2, boolean animate) {
+            executeForTaskbarManager(taskbarManager ->
+                    taskbarManager.disableNavBarElements(displayId, state1, state2, animate));
+        }
+
+        @Override
+        public void onSystemBarAttributesChanged(int displayId, int behavior) {
+            executeForTaskbarManager(taskbarManager ->
+                    taskbarManager.onSystemBarAttributesChanged(displayId, behavior));
+        }
+
+        @Override
+        public void onTransitionModeUpdated(int barMode, boolean checkBarModes) {
+            executeForTaskbarManager(taskbarManager ->
+                    taskbarManager.onTransitionModeUpdated(barMode, checkBarModes));
+        }
+
+        @Override
+        public void onNavButtonsDarkIntensityChanged(float darkIntensity) {
+            executeForTaskbarManager(taskbarManager ->
+                    taskbarManager.onNavButtonsDarkIntensityChanged(darkIntensity));
+        }
+
+        @Override
+        public void onNavigationBarLumaSamplingEnabled(int displayId, boolean enable) {
+            executeForTaskbarManager(taskbarManager ->
+                    taskbarManager.onNavigationBarLumaSamplingEnabled(displayId, enable));
+        }
+
+        @Override
+        public void onUnbind(IRemoteCallback reply) {
+            // Run everything in the same main thread block to ensure the cleanup happens before
+            // sending the reply.
+            MAIN_EXECUTOR.execute(() -> {
+                executeForTaskbarManager(TaskbarManager::destroy);
+                try {
+                    reply.sendResult(null);
+                } catch (RemoteException e) {
+                    Log.w(TAG, "onUnbind: Failed to reply to LauncherProxyService", e);
+                }
+            });
+        }
+
+        private void executeForTouchInteractionService(
+                @NonNull Consumer<TouchInteractionService> tisConsumer) {
+            TouchInteractionService tis = mTis.get();
+            if (tis == null) return;
+            tisConsumer.accept(tis);
+        }
+
+        private void executeForTaskbarManager(
+                @NonNull Consumer<TaskbarManager> taskbarManagerConsumer) {
+            MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(tis -> {
+                TaskbarManager taskbarManager = tis.mTaskbarManager;
+                if (taskbarManager == null) return;
+                taskbarManagerConsumer.accept(taskbarManager);
+            }));
+        }
+
+        /**
+         * Returns the {@link TaskbarManager}.
+         * <p>
+         * Returns {@code null} if TouchInteractionService is not connected
+         */
+        @Nullable
+        public TaskbarManager getTaskbarManager() {
+            TouchInteractionService tis = mTis.get();
+            if (tis == null) return null;
+            return tis.mTaskbarManager;
+        }
+
+        @VisibleForTesting
+        public void injectFakeTrackpadForTesting() {
+            TouchInteractionService tis = mTis.get();
+            if (tis == null) return;
+            tis.mTrackpadsConnected.add(1000);
+            tis.initInputMonitor("tapl testing");
+        }
+
+        @VisibleForTesting
+        public void ejectFakeTrackpadForTesting() {
+            TouchInteractionService tis = mTis.get();
+            if (tis == null) return;
+            tis.mTrackpadsConnected.clear();
+            // This method destroys the current input monitor if set up, and only init a new one
+            // in 3-button mode if {@code mTrackpadsConnected} is not empty. So in other words,
+            // it will destroy the input monitor.
+            tis.initInputMonitor("tapl testing");
+        }
+
+        /**
+         * Sets whether a predictive back-to-home animation is in progress in the device state
+         */
+        public void setPredictiveBackToHomeInProgress(boolean isInProgress) {
+            executeForTouchInteractionService(tis ->
+                    tis.mDeviceState.setPredictiveBackToHomeInProgress(isInProgress));
+        }
+
+        /**
+         * Returns the {@link OverviewCommandHelper}.
+         * <p>
+         * Returns {@code null} if TouchInteractionService is not connected
+         */
+        @Nullable
+        public OverviewCommandHelper getOverviewCommandHelper() {
+            TouchInteractionService tis = mTis.get();
+            if (tis == null) return null;
+            return tis.mOverviewCommandHelper;
+        }
+
+        /**
+         * Sets a proxy to bypass swipe up behavior
+         */
+        public void setSwipeUpProxy(Function<GestureState, AnimatedFloat> proxy) {
+            executeForTouchInteractionService(
+                    tis -> tis.mSwipeUpProxyProvider = proxy != null ? proxy : (i -> null));
+        }
+
+        /**
+         * Sets the task id where gestures should be blocked
+         */
+        public void setGestureBlockedTaskId(int taskId) {
+            executeForTouchInteractionService(
+                    tis -> tis.mDeviceState.setGestureBlockingTaskId(taskId));
+        }
+
+        /** Refreshes the current overview target. */
+        public void refreshOverviewTarget() {
+            executeForTouchInteractionService(tis -> {
+                tis.mAllAppsActionManager.onDestroy();
+                tis.onOverviewTargetChanged(tis.mOverviewComponentObserver.isHomeAndOverviewSame());
+            });
+        }
+    }
+
     private RotationTouchHelper mRotationTouchHelper;
 
     private final AbsSwipeUpHandler.Factory mLauncherSwipeHandlerFactory =
@@ -130,13 +521,6 @@
     private final ScreenOnTracker.ScreenOnListener mScreenOnListener = this::onScreenOnChanged;
     private final OverviewChangeListener mOverviewChangeListener = this::onOverviewTargetChanged;
 
-    private final Runnable mSysUiProxyStateChangeCallback =
-            () -> {
-                if (SystemUiProxy.INSTANCE.get(this).isActive()) {
-                    initInputMonitor("TISBinder#onInitialize()");
-                }
-            };
-
     private final TaskbarNavButtonCallbacks mNavCallbacks = new TaskbarNavButtonCallbacks() {
         @Override
         public void onNavigateHome() {
@@ -170,6 +554,7 @@
     private InputEventReceiver mInputEventReceiver;
 
     private TaskbarManager mTaskbarManager;
+    private Function<GestureState, AnimatedFloat> mSwipeUpProxyProvider = i -> null;
     private AllAppsActionManager mAllAppsActionManager;
     private ActiveTrackpadList mTrackpadsConnected;
 
@@ -212,7 +597,6 @@
         mDisplayInfoChangeListener =
                 mDeviceState.addNavigationModeChangedCallback(this::onNavigationModeChanged);
         ScreenOnTracker.INSTANCE.get(this).addListener(mScreenOnListener);
-        SystemUiProxy.INSTANCE.get(this).addOnStateChangeListener(mSysUiProxyStateChangeCallback);
     }
 
     private void disposeEventHandlers(String reason) {
@@ -265,6 +649,8 @@
         mResetGestureInputConsumer = new ResetGestureInputConsumer(
                 mTaskAnimationManager, mTaskbarManager::getCurrentActivityContext);
         mInputConsumer.registerInputConsumer();
+        onSystemUiFlagsChanged(mDeviceState.getSystemUiStateFlags());
+        onAssistantVisibilityChanged();
 
         // Initialize the task tracker
         TopTaskTracker.INSTANCE.get(this);
@@ -283,10 +669,6 @@
         return mOverviewCommandHelper;
     }
 
-    public TaskbarManager getTaskbarManager() {
-        return mTaskbarManager;
-    }
-
     private void resetHomeBounceSeenOnQuickstepEnabledFirstTime() {
         if (!LockedUserState.get(this).isUserUnlocked() || mDeviceState.isButtonNavMode()) {
             // Skip if not yet unlocked (can't read user shared prefs) or if the current navigation
@@ -328,18 +710,34 @@
         });
     }
 
+    @UiThread
+    private void onSystemUiFlagsChanged(@SystemUiStateFlags long lastSysUIFlags) {
+        if (LockedUserState.get(this).isUserUnlocked()) {
+            long systemUiStateFlags = mDeviceState.getSystemUiStateFlags();
+            SystemUiProxy.INSTANCE.get(this).setLastSystemUiStateFlags(systemUiStateFlags);
+            mOverviewComponentObserver.setHomeDisabled(mDeviceState.isHomeDisabled());
+            mTaskbarManager.onSystemUiFlagsChanged(systemUiStateFlags);
+            mTaskAnimationManager.onSystemUiFlagsChanged(lastSysUIFlags, systemUiStateFlags);
+        }
+    }
+
+    @UiThread
+    private void onAssistantVisibilityChanged() {
+        if (LockedUserState.get(this).isUserUnlocked()) {
+            mOverviewComponentObserver.getContainerInterface().onAssistantVisibilityChanged(
+                    mDeviceState.getAssistantVisibility());
+        }
+    }
+
     @Override
     public void onDestroy() {
         Log.d(TAG, "onDestroy: user=" + getUserId()
                 + " instance=" + System.identityHashCode(this));
         if (LockedUserState.get(this).isUserUnlocked()) {
             mInputConsumer.unregisterInputConsumer();
+            mOverviewComponentObserver.setHomeDisabled(false);
             mOverviewComponentObserver.removeOverviewChangeListener(mOverviewChangeListener);
         }
-        if (mTaskAnimationManager != null) {
-            mTaskAnimationManager.onDestroy();
-        }
-
         disposeEventHandlers("TouchInteractionService onDestroy()");
         SystemUiProxy.INSTANCE.get(this).clearProxy();
 
@@ -354,8 +752,6 @@
         mDeviceState.removeDisplayInfoChangeListener(mDisplayInfoChangeListener);
         LockedUserState.get(this).removeOnUserUnlockedRunnable(mUserUnlockedRunnable);
         ScreenOnTracker.INSTANCE.get(this).removeListener(mScreenOnListener);
-        SystemUiProxy.INSTANCE.get(this)
-                .removeOnStateChangeListener(mSysUiProxyStateChangeCallback);
         super.onDestroy();
     }
 
@@ -483,6 +879,7 @@
                         this::onConsumerInactive,
                         mInputEventReceiver,
                         mTaskbarManager,
+                        mSwipeUpProxyProvider,
                         mOverviewCommandHelper,
                         event);
                 mUncheckedConsumer = mConsumer;
@@ -715,7 +1112,7 @@
         pw.println("Input state:");
         pw.println("\tmInputMonitorCompat=" + mInputMonitorCompat);
         pw.println("\tmInputEventReceiver=" + mInputEventReceiver);
-        DisplayController.INSTANCE.get(this).dump(pw);
+        DisplayController.get(this).dump(pw);
         pw.println("TouchState:");
         RecentsViewContainer createdOverviewContainer = mOverviewComponentObserver == null ? null
                 : mOverviewComponentObserver.getContainerInterface().getCreatedContainer();
@@ -761,26 +1158,4 @@
                 gestureState, touchTimeMs, mTaskAnimationManager.isRecentsAnimationRunning(),
                 mInputConsumer, MSDLPlayerWrapper.INSTANCE.get(this));
     }
-
-    @VisibleForTesting
-    public void injectFakeTrackpadForTesting() {
-        mTrackpadsConnected.add(1000);
-        initInputMonitor("tapl testing");
-    }
-
-    @VisibleForTesting
-    public void ejectFakeTrackpadForTesting() {
-        mTrackpadsConnected.clear();
-        // This method destroys the current input monitor if set up, and only init a new one
-        // in 3-button mode if {@code mTrackpadsConnected} is not empty. So in other words,
-        // it will destroy the input monitor.
-        initInputMonitor("tapl testing");
-    }
-
-    /** Refreshes the current overview target. */
-    @VisibleForTesting
-    public void refreshOverviewTarget() {
-        mAllAppsActionManager.onDestroy();
-        onOverviewTargetChanged(mOverviewComponentObserver.isHomeAndOverviewSame());
-    }
 }
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackNavBarTouchController.java b/quickstep/src/com/android/quickstep/fallback/FallbackNavBarTouchController.java
index ec531d8..a61c1a0 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackNavBarTouchController.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackNavBarTouchController.java
@@ -44,7 +44,7 @@
                 DisplayController.getNavigationMode(mContainer.asContext());
         if (sysUINavigationMode == NavigationMode.NO_BUTTON) {
             NavBarPosition navBarPosition = new NavBarPosition(sysUINavigationMode,
-                    DisplayController.INSTANCE.get(mContainer.asContext()).getInfo());
+                    DisplayController.get(mContainer.asContext()).getInfo());
             mTriggerSwipeUpTracker = new TriggerSwipeUpTouchTracker(mContainer.asContext(),
                     true /* disableHorizontalSwipe */, navBarPosition, this);
         } else {
diff --git a/quickstep/src/com/android/quickstep/fallback/window/RecentsDisplayModel.kt b/quickstep/src/com/android/quickstep/fallback/window/RecentsDisplayModel.kt
index 95a3ec2..c7e77b2 100644
--- a/quickstep/src/com/android/quickstep/fallback/window/RecentsDisplayModel.kt
+++ b/quickstep/src/com/android/quickstep/fallback/window/RecentsDisplayModel.kt
@@ -18,18 +18,23 @@
 
 import android.content.Context
 import android.util.Log
-import android.view.Display
+import android.view.Display.INVALID_DISPLAY
 import com.android.launcher3.Flags
+import com.android.launcher3.LauncherPrefs
 import com.android.launcher3.dagger.ApplicationContext
 import com.android.launcher3.dagger.LauncherAppSingleton
 import com.android.launcher3.util.DaggerSingletonObject
 import com.android.launcher3.util.DaggerSingletonTracker
+import com.android.launcher3.util.DisplayController
 import com.android.launcher3.util.Executors
+import com.android.launcher3.util.PerDisplayObjectProvider
 import com.android.launcher3.util.WallpaperColorHints
+import com.android.launcher3.util.window.WindowManagerProxy
 import com.android.quickstep.DisplayModel
 import com.android.quickstep.FallbackWindowInterface
 import com.android.quickstep.dagger.QuickstepBaseAppComponent
 import com.android.quickstep.fallback.window.RecentsDisplayModel.RecentsDisplayResource
+import com.android.quickstep.util.validDisplayId
 import javax.inject.Inject
 
 @LauncherAppSingleton
@@ -37,9 +42,11 @@
 @Inject
 constructor(
     @ApplicationContext context: Context,
+    private val windowManagerProxy: WindowManagerProxy,
+    private val launcherPrefs: LauncherPrefs,
     private val wallpaperColorHints: WallpaperColorHints,
     tracker: DaggerSingletonTracker,
-) : DisplayModel<RecentsDisplayResource>(context) {
+) : DisplayModel<RecentsDisplayResource>(context), PerDisplayObjectProvider {
 
     companion object {
         private const val TAG = "RecentsDisplayModel"
@@ -57,16 +64,17 @@
     }
 
     init {
-        if (enableOverviewInWindow()) {
-            displayManager.registerDisplayListener(displayListener, Executors.MAIN_EXECUTOR.handler)
-            // In the scenario where displays were added before this display listener was
-            // registered, we should store the RecentsDisplayResources for those displays
-            // directly.
-            displayManager.displays
-                .filter { getDisplayResource(it.displayId) == null }
-                .forEach { storeRecentsDisplayResource(it.displayId, it) }
-            tracker.addCloseable { destroy() }
-        }
+        // Add the display for the context with which we are initialized.
+        storeRecentsDisplayResource(context.validDisplayId)
+
+        displayManager.registerDisplayListener(displayListener, Executors.MAIN_EXECUTOR.handler)
+        // In the scenario where displays were added before this display listener was
+        // registered, we should store the RecentsDisplayResources for those displays
+        // directly.
+        displayManager.displays
+            .filter { getDisplayResource(it.displayId) == null }
+            .forEach { storeRecentsDisplayResource(it.displayId) }
+        tracker.addCloseable { destroy() }
     }
 
     override fun createDisplayResource(displayId: Int) {
@@ -74,26 +82,34 @@
         getDisplayResource(displayId)?.let {
             return
         }
+        if (displayId == INVALID_DISPLAY) {
+            Log.e(TAG, "createDisplayResource: INVALID_DISPLAY")
+            return
+        }
+        storeRecentsDisplayResource(displayId)
+    }
+
+    private fun storeRecentsDisplayResource(displayId: Int): RecentsDisplayResource {
         val display = displayManager.getDisplay(displayId)
         if (display == null) {
             if (DEBUG)
                 Log.w(
                     TAG,
-                    "createDisplayResource: could not create display for displayId=$displayId",
-                    Exception(),
+                    "storeRecentsDisplayResource: could not create display for displayId=$displayId",
                 )
-            return
         }
-        storeRecentsDisplayResource(displayId, display)
-    }
-
-    private fun storeRecentsDisplayResource(displayId: Int, display: Display) {
-        displayResourceArray[displayId] =
-            RecentsDisplayResource(
-                displayId,
-                context.createDisplayContext(display),
-                wallpaperColorHints.hints,
-            )
+        return displayResourceArray[displayId]
+            ?: RecentsDisplayResource(
+                    displayId,
+                    context,
+                    windowManagerProxy,
+                    launcherPrefs,
+                    if (enableOverviewInWindow() && display != null)
+                        context.createDisplayContext(display)
+                    else null,
+                    wallpaperColorHints.hints,
+                )
+                .also { displayResourceArray[displayId] = it }
     }
 
     fun getRecentsWindowManager(displayId: Int): RecentsWindowManager? {
@@ -104,17 +120,36 @@
         return getDisplayResource(displayId)?.fallbackWindowInterface
     }
 
+    override fun getDisplayController(displayId: Int): DisplayController {
+        if (DEBUG) Log.d(TAG, "getDisplayController $displayId")
+        return (getDisplayResource(displayId)
+                ?: storeRecentsDisplayResource(displayId).also {
+                    // We shouldn't get here because the display should already have been
+                    // initialized.
+                    Log.e(TAG, "getDisplayController no such display: $displayId")
+                })
+            .displayController
+    }
+
     data class RecentsDisplayResource(
         var displayId: Int,
-        var displayContext: Context,
+        val appContext: Context,
+        val windowManagerProxy: WindowManagerProxy,
+        val launcherPrefs: LauncherPrefs,
+        var displayContext: Context?, // null when OverviewInWindow not enabled
         val wallpaperColorHints: Int,
     ) : DisplayResource() {
-        val recentsWindowManager = RecentsWindowManager(displayContext, wallpaperColorHints)
-        val fallbackWindowInterface: FallbackWindowInterface =
-            FallbackWindowInterface(recentsWindowManager)
+        val recentsWindowManager =
+            displayContext?.let { RecentsWindowManager(it, wallpaperColorHints) }
+        val fallbackWindowInterface: FallbackWindowInterface? =
+            recentsWindowManager?.let { FallbackWindowInterface(recentsWindowManager) }
+        val lifecycle = DaggerSingletonTracker()
+        val displayController =
+            DisplayController(appContext, windowManagerProxy, launcherPrefs, lifecycle, displayId)
 
         override fun cleanup() {
-            recentsWindowManager.destroy()
+            recentsWindowManager?.destroy()
+            lifecycle.close()
         }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
index 503b900..6be1098 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
@@ -122,7 +122,7 @@
         mRotationTouchHelper = RotationTouchHelper.INSTANCE.get(mContext);
 
         // Do not use DeviceProfile as the user data might be locked
-        mDisplaySize = DisplayController.INSTANCE.get(context).getInfo().currentSize;
+        mDisplaySize = DisplayController.get(context).getInfo().currentSize;
 
         // Init states
         mStateCallback = new MultiStateCallback(STATE_NAMES);
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
index afe988d..9a572b9 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
@@ -73,7 +73,7 @@
             InputMonitorCompat inputMonitor, RecentsAnimationDeviceState deviceState,
             NavHandle navHandle, GestureState gestureState) {
         super(delegate, inputMonitor);
-        mScreenWidth = DisplayController.INSTANCE.get(context).getInfo().currentSize.x;
+        mScreenWidth = DisplayController.get(context).getInfo().currentSize.x;
         mDeepPressEnabled = DeviceConfigWrapper.get().getEnableLpnhDeepPress();
         ContextualSearchStateManager contextualSearchStateManager =
                 ContextualSearchStateManager.INSTANCE.get(context);
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OneHandedModeInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OneHandedModeInputConsumer.java
index 83b556d..fa9744d 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OneHandedModeInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OneHandedModeInputConsumer.java
@@ -69,7 +69,7 @@
         mDragDistThreshold = context.getResources().getDimensionPixelSize(
                 R.dimen.gestures_onehanded_drag_threshold);
         mSquaredSlop = mDeviceState.getSquaredTouchSlop();
-        mDisplaySize = DisplayController.INSTANCE.get(mContext).getInfo().currentSize;
+        mDisplaySize = DisplayController.get(mContext).getInfo().currentSize;
         mNavBarSize = ResourceUtils.getNavbarSize(NAVBAR_BOTTOM_GESTURE_SIZE,
                 mContext.getResources());
     }
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/ProgressDelegateInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/ProgressDelegateInputConsumer.java
index c91bebe..ca62657 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/ProgressDelegateInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/ProgressDelegateInputConsumer.java
@@ -99,7 +99,7 @@
         mProgress = progress;
 
         // Do not use DeviceProfile as the user data might be locked
-        mDisplaySize = DisplayController.INSTANCE.get(context).getInfo().currentSize;
+        mDisplaySize = DisplayController.get(context).getInfo().currentSize;
 
         // Init states
         mStateCallback = new MultiStateCallback(STATE_NAMES);
diff --git a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
index 39a9dff..c986b88 100644
--- a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
+++ b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
@@ -72,8 +72,7 @@
 import com.android.quickstep.GestureState;
 import com.android.quickstep.OverviewComponentObserver;
 import com.android.quickstep.OverviewComponentObserver.OverviewChangeListener;
-import com.android.quickstep.RecentsAnimationDeviceState;
-import com.android.quickstep.TISBinder;
+import com.android.quickstep.TouchInteractionService.TISBinder;
 import com.android.quickstep.util.ActivityPreloadUtil;
 import com.android.quickstep.util.LottieAnimationColorUtils;
 import com.android.quickstep.util.TISBindHelper;
@@ -284,13 +283,16 @@
     protected void onResume() {
         super.onResume();
         maybeResumeOrPauseBackgroundAnimation();
-        RecentsAnimationDeviceState.INSTANCE.get(this)
-                .setSwipeUpProxyProvider(this::createSwipeUpProxy);
-        setSetupUIVisible(true);
+        TISBinder binder = mTISBindHelper.getBinder();
+        if (binder != null) {
+            setSetupUIVisible(true);
+            binder.setSwipeUpProxy(this::createSwipeUpProxy);
+        }
     }
 
     private void onTISConnected(TISBinder binder) {
         setSetupUIVisible(isResumed());
+        binder.setSwipeUpProxy(isResumed() ? this::createSwipeUpProxy : null);
         TaskbarManager taskbarManager = binder.getTaskbarManager();
         if (taskbarManager != null) {
             mLauncherStartAnim = taskbarManager.createLauncherStartFromSuwAnim(MAX_SWIPE_DURATION);
@@ -304,8 +306,7 @@
     @Override
     protected void onPause() {
         super.onPause();
-        setSetupUIVisible(false);
-        RecentsAnimationDeviceState.INSTANCE.get(this).setSwipeUpProxyProvider(null);
+        clearBinderOverride();
         maybeResumeOrPauseBackgroundAnimation();
         if (mSwipeProgress.value >= 1) {
             finishAndRemoveTask();
@@ -313,6 +314,14 @@
         }
     }
 
+    private void clearBinderOverride() {
+        TISBinder binder = mTISBindHelper.getBinder();
+        if (binder != null) {
+            setSetupUIVisible(false);
+            binder.setSwipeUpProxy(null);
+        }
+    }
+
     /**
      * Should be called when we have successfully reached Launcher, so we dispatch to animation
      * listeners to ensure the state matches the visual animation that just occurred.
@@ -329,9 +338,8 @@
     protected void onDestroy() {
         super.onDestroy();
         getIDP().removeOnChangeListener(mOnIDPChangeListener);
-        // In case bindHelper was not connected during onPause, clear UIVisible flag again
-        setSetupUIVisible(false);
         mTISBindHelper.onDestroy();
+        clearBinderOverride();
         if (mBackgroundAnimatorListener != null) {
             mAnimatedBackground.removeAnimatorListener(mBackgroundAnimatorListener);
         }
diff --git a/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
index 2114c30..0365f89 100644
--- a/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
+++ b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
@@ -38,9 +38,10 @@
 import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.R;
 import com.android.launcher3.logging.StatsLogManager;
-import com.android.quickstep.RecentsAnimationDeviceState;
+import com.android.quickstep.TouchInteractionService.TISBinder;
 import com.android.quickstep.interaction.TutorialController.TutorialType;
 import com.android.quickstep.util.LayoutUtils;
+import com.android.quickstep.util.TISBindHelper;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -63,6 +64,7 @@
 
     private SharedPreferences mSharedPrefs;
     private StatsLogManager mStatsLogManager;
+    private TISBindHelper mTISBindHelper;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -97,6 +99,7 @@
                 .commit();
 
         correctUserOrientation();
+        mTISBindHelper = new TISBindHelper(this, this::onTISConnected);
 
         initWindowInsets();
     }
@@ -346,6 +349,10 @@
         updateServiceState(true);
     }
 
+    private void onTISConnected(TISBinder binder) {
+        updateServiceState(isResumed());
+    }
+
     @Override
     protected void onPause() {
         super.onPause();
@@ -353,13 +360,16 @@
     }
 
     private void updateServiceState(boolean isEnabled) {
-        RecentsAnimationDeviceState.INSTANCE.get(this)
-                .setGestureBlockingTaskId(isEnabled ? getTaskId() : -1);
+        TISBinder binder = mTISBindHelper.getBinder();
+        if (binder != null) {
+            binder.setGestureBlockedTaskId(isEnabled ? getTaskId() : -1);
+        }
     }
 
     @Override
     protected void onDestroy() {
         super.onDestroy();
+        mTISBindHelper.onDestroy();
         updateServiceState(false);
     }
 }
diff --git a/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
index fdbd509..bf6c3c3 100644
--- a/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
+++ b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
@@ -66,7 +66,7 @@
                 /* disableHorizontalSwipe= */ true,
                 new NavBarPosition(
                         NavigationMode.NO_BUTTON,
-                        DisplayController.INSTANCE.get(mContext).getInfo()),
+                        DisplayController.get(mContext).getInfo()),
                 /* onSwipeUp= */ this);
         mMotionPauseDetector = new MotionPauseDetector(context);
 
diff --git a/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java b/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
index 0cc349d..c27fb60 100644
--- a/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
+++ b/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
@@ -16,6 +16,8 @@
 
 package com.android.quickstep.logging;
 
+import static android.view.Display.DEFAULT_DISPLAY;
+
 import static com.android.launcher3.LauncherPrefs.getDevicePrefs;
 import static com.android.launcher3.LauncherPrefs.getPrefs;
 import static com.android.launcher3.graphics.ThemeManager.KEY_THEMED_ICONS;
@@ -54,6 +56,7 @@
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.DisplayController.Info;
 import com.android.launcher3.util.NavigationMode;
+import com.android.launcher3.util.PerDisplayObjectProvider;
 import com.android.launcher3.util.SettingsCache;
 import com.android.quickstep.dagger.QuickstepBaseAppComponent;
 
@@ -94,9 +97,10 @@
     @Inject
     SettingsChangeLogger(@ApplicationContext Context context,
             DaggerSingletonTracker tracker,
-            DisplayController displayController,
+            PerDisplayObjectProvider displayControllerProvider,
             SettingsCache settingsCache) {
-        this(context, StatsLogManager.newInstance(context), tracker, displayController,
+        this(context, StatsLogManager.newInstance(context), tracker,
+                displayControllerProvider.getDisplayController(DEFAULT_DISPLAY),
                 settingsCache);
     }
 
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index 594c99a..fb521ab 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -247,7 +247,7 @@
         StatsCompatLogger(Context context, ActivityContext activityContext) {
             mContext = context;
             mActivityContext = Optional.ofNullable(activityContext);
-            mDisplayRotation = DisplayController.INSTANCE.get(mContext).getInfo().rotation;
+            mDisplayRotation = DisplayController.get(mContext).getInfo().rotation;
         }
 
         @Override
diff --git a/quickstep/src/com/android/quickstep/util/ContextualSearchInvoker.kt b/quickstep/src/com/android/quickstep/util/ContextualSearchInvoker.kt
index 969aa0f..d00a39c 100644
--- a/quickstep/src/com/android/quickstep/util/ContextualSearchInvoker.kt
+++ b/quickstep/src/com/android/quickstep/util/ContextualSearchInvoker.kt
@@ -21,6 +21,7 @@
 import android.app.contextualsearch.ContextualSearchManager.FEATURE_CONTEXTUAL_SEARCH
 import android.content.Context
 import android.util.Log
+import androidx.annotation.VisibleForTesting
 import com.android.internal.app.AssistUtils
 import com.android.launcher3.logging.StatsLogManager
 import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_LAUNCH_ASSISTANT_FAILED_SERVICE_ERROR
@@ -33,7 +34,7 @@
 import com.android.quickstep.BaseContainerInterface
 import com.android.quickstep.DeviceConfigWrapper
 import com.android.quickstep.OverviewComponentObserver
-import com.android.quickstep.RecentsAnimationDeviceState
+import com.android.quickstep.SystemUiProxy
 import com.android.quickstep.TopTaskTracker
 import com.android.quickstep.views.RecentsView
 import com.android.systemui.shared.system.QuickStepContract
@@ -44,10 +45,9 @@
     private val context: Context,
     private val contextualSearchStateManager: ContextualSearchStateManager,
     private val topTaskTracker: TopTaskTracker,
-    private val deviceState: RecentsAnimationDeviceState,
+    private val systemUiProxy: SystemUiProxy,
     private val statsLogManager: StatsLogManager,
     private val contextualSearchHapticManager: ContextualSearchHapticManager,
-    private val overviewComponentObserver: OverviewComponentObserver,
     private val contextualSearchManager: ContextualSearchManager?,
 ) {
     constructor(
@@ -56,10 +56,9 @@
         context,
         ContextualSearchStateManager.INSTANCE[context],
         TopTaskTracker.INSTANCE[context],
-        RecentsAnimationDeviceState.INSTANCE[context],
+        SystemUiProxy.INSTANCE[context],
         StatsLogManager.newInstance(context),
         ContextualSearchHapticManager.INSTANCE[context],
-        OverviewComponentObserver.INSTANCE[context],
         context.getSystemService(ContextualSearchManager::class.java),
     )
 
@@ -190,7 +189,7 @@
         if (contextualSearchManager == null) {
             return false
         }
-        val recentsContainerInterface = overviewComponentObserver.containerInterface
+        val recentsContainerInterface = getRecentsContainerInterface()
         if (recentsContainerInterface?.isInLiveTileMode() == true) {
             Log.i(TAG, "Contextual Search invocation attempted: live tile")
             endLiveTileMode(recentsContainerInterface) {
@@ -203,20 +202,27 @@
     }
 
     private fun isFakeLandscape(): Boolean =
-        overviewComponentObserver.containerInterface
-            ?.createdContainer
+        getRecentsContainerInterface()
+            ?.getCreatedContainer()
             ?.getOverviewPanel<RecentsView<*, *>>()
-            ?.pagedOrientationHandler
+            ?.getPagedOrientationHandler()
             ?.isLayoutNaturalToLauncher == false
 
-    private fun isInSplitscreen(): Boolean = topTaskTracker.runningSplitTaskIds.isNotEmpty()
+    private fun isInSplitscreen(): Boolean {
+        return topTaskTracker.getRunningSplitTaskIds().isNotEmpty()
+    }
 
     private fun isNotificationShadeShowing(): Boolean {
-        return deviceState.systemUiStateFlags and SHADE_EXPANDED_SYSUI_FLAGS != 0L
+        return systemUiProxy.lastSystemUiStateFlags and SHADE_EXPANDED_SYSUI_FLAGS != 0L
     }
 
     private fun isKeyguardShowing(): Boolean {
-        return deviceState.systemUiStateFlags and KEYGUARD_SHOWING_SYSUI_FLAGS != 0L
+        return systemUiProxy.lastSystemUiStateFlags and KEYGUARD_SHOWING_SYSUI_FLAGS != 0L
+    }
+
+    @VisibleForTesting
+    fun getRecentsContainerInterface(): BaseContainerInterface<*, *>? {
+        return OverviewComponentObserver.INSTANCE.get(context).containerInterface
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/util/ExternalDisplays.kt b/quickstep/src/com/android/quickstep/util/ExternalDisplays.kt
index 455b312..9df1fe8 100644
--- a/quickstep/src/com/android/quickstep/util/ExternalDisplays.kt
+++ b/quickstep/src/com/android/quickstep/util/ExternalDisplays.kt
@@ -16,6 +16,8 @@
 
 package com.android.quickstep.util
 
+import android.content.Context
+import android.util.Log
 import android.view.Display.DEFAULT_DISPLAY
 import android.view.Display.INVALID_DISPLAY
 import com.android.systemui.shared.recents.model.Task
@@ -24,17 +26,30 @@
 val Int.isExternalDisplay
     get() = this != DEFAULT_DISPLAY
 
-/** Returns displayId of this [Task], default to [DEFAULT_DISPLAY] */
-val Task?.displayId
+val Int?.validDisplayId: Int
     get() =
-        this?.key?.displayId.let { displayId ->
-            when (displayId) {
-                null -> DEFAULT_DISPLAY
-                INVALID_DISPLAY -> DEFAULT_DISPLAY
-                else -> displayId
-            }
+        when (this) {
+            null -> DEFAULT_DISPLAY
+            INVALID_DISPLAY -> DEFAULT_DISPLAY
+            else -> this
         }
 
+/** Returns displayId of this [Task], default to [DEFAULT_DISPLAY] */
+val Task?.validDisplayId
+    get() = this?.key?.displayId?.validDisplayId ?: DEFAULT_DISPLAY
+
 /** Returns if this task belongs tto [DEFAULT_DISPLAY] */
 val Task?.isExternalDisplay
-    get() = displayId.isExternalDisplay
+    get(): Boolean = this.validDisplayId.isExternalDisplay ?: false
+
+val Context?.validDisplayId
+    get() =
+        try {
+            this?.display?.displayId?.validDisplayId ?: DEFAULT_DISPLAY
+        } catch (ignored: UnsupportedOperationException) {
+            Log.w(
+                "ExternalDisplays",
+                "Tried to get a Display from a Context not associated with one",
+            )
+            DEFAULT_DISPLAY
+        }
diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
index a5be89a..8fb16d7 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
+++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
@@ -573,7 +573,7 @@
      */
     public DeviceProfile getLauncherDeviceProfile() {
         InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(mContext);
-        Point currentSize = DisplayController.INSTANCE.get(mContext).getInfo().currentSize;
+        Point currentSize = DisplayController.get(mContext).getInfo().currentSize;
 
         int width, height;
         if ((mRecentsActivityRotation == ROTATION_90 || mRecentsActivityRotation == ROTATION_270)) {
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
index 3c0a63a..88d14b7 100644
--- a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
@@ -985,6 +985,12 @@
         val splitTree: Pair<Change, List<Change>>? = extractTopParentAndChildren(transitionInfo)
         check(splitTree != null) { "Could not find a split root candidate" }
         val rootCandidate = splitTree.first
+        val stageRootTaskIds: Set<Int> = splitTree.second
+            .map { it.taskInfo!!.taskId }
+            .toSet()
+        val leafTasks: List<Change> = transitionInfo.changes
+            .filter { it.taskInfo != null && it.taskInfo!!.parentTaskId in stageRootTaskIds}
+            .toList()
 
         // Starting position is a 34% size tile centered in the middle of the screen.
         // Ending position is the full device screen.
@@ -1018,6 +1024,29 @@
                 override fun onAnimationEnd(animation: Animator) {
                     finishCallback.run()
                 }
+
+                override fun onAnimationStart(animation: Animator) {
+                    // Reset leaf and stage root tasks, animation can begin from freeform windows
+                    for (leaf in leafTasks) {
+                        val endAbsBounds = leaf.endAbsBounds
+
+                        t.setAlpha(leaf.leash, 1f)
+                        t.setCrop(leaf.leash, 0f, 0f,
+                            endAbsBounds.width().toFloat(), endAbsBounds.height().toFloat())
+                        t.setPosition(leaf.leash, 0f, 0f)
+                    }
+
+                    for (stageRoot in splitTree.second) {
+                        val endAbsBounds = stageRoot.endAbsBounds
+
+                        t.setAlpha(stageRoot.leash, 1f)
+                        t.setCrop(stageRoot.leash, 0f, 0f,
+                            endAbsBounds.width().toFloat(), endAbsBounds.height().toFloat())
+                        t.setPosition(stageRoot.leash, endAbsBounds.left.toFloat(),
+                            endAbsBounds.top.toFloat())
+                    }
+                    t.apply()
+                }
             }
         )
 
diff --git a/quickstep/src/com/android/quickstep/util/TISBindHelper.java b/quickstep/src/com/android/quickstep/util/TISBindHelper.java
index 3716efa..027dc08 100644
--- a/quickstep/src/com/android/quickstep/util/TISBindHelper.java
+++ b/quickstep/src/com/android/quickstep/util/TISBindHelper.java
@@ -28,8 +28,8 @@
 
 import com.android.launcher3.taskbar.TaskbarManager;
 import com.android.quickstep.OverviewCommandHelper;
-import com.android.quickstep.TISBinder;
 import com.android.quickstep.TouchInteractionService;
+import com.android.quickstep.TouchInteractionService.TISBinder;
 
 import java.util.ArrayList;
 import java.util.function.Consumer;
@@ -109,6 +109,15 @@
         return mBinder == null ? null : mBinder.getTaskbarManager();
     }
 
+    /**
+     * Sets flag whether a predictive back-to-home animation is in progress
+     */
+    public void setPredictiveBackToHomeInProgress(boolean isInProgress) {
+        if (mBinder != null) {
+            mBinder.setPredictiveBackToHomeInProgress(isInProgress);
+        }
+    }
+
     @Nullable
     public OverviewCommandHelper getOverviewCommandHelper() {
         return mBinder == null ? null : mBinder.getOverviewCommandHelper();
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
index 02be373..da160f1 100644
--- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
@@ -55,8 +55,8 @@
 import com.android.quickstep.recents.ui.viewmodel.DesktopTaskViewModel
 import com.android.quickstep.recents.ui.viewmodel.TaskData
 import com.android.quickstep.task.thumbnail.TaskThumbnailView
+import com.android.quickstep.util.DesktopTask
 import com.android.quickstep.util.RecentsOrientedState
-import com.android.systemui.shared.recents.model.Task
 
 /** TaskView that contains all tasks that are part of the desktop. */
 class DesktopTaskView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
@@ -271,10 +271,14 @@
 
     /** Updates this desktop task to the gives task list defined in `tasks` */
     fun bind(
-        tasks: List<Task>,
+        desktopTask: DesktopTask,
         orientedState: RecentsOrientedState,
         taskOverlayFactory: TaskOverlayFactory,
     ) {
+        // TODO(b/370495260): Minimized tasks should not be filtered with desktop exploded view
+        // support.
+        // Minimized tasks should not be shown in Overview.
+        val tasks = desktopTask.tasks.filterNot { it.isMinimized }
         if (DEBUG) {
             val sb = StringBuilder()
             sb.append("bind tasks=").append(tasks.size).append("\n")
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 51980f0..424271a 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -132,6 +132,7 @@
 import android.widget.Toast;
 import android.window.DesktopModeFlags;
 import android.window.PictureInPictureSurfaceTransaction;
+import android.window.TransitionInfo;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -212,6 +213,7 @@
 import com.android.quickstep.recents.viewmodel.RecentsViewModel;
 import com.android.quickstep.util.ActiveGestureProtoLogProxy;
 import com.android.quickstep.util.AnimUtils;
+import com.android.quickstep.util.DesktopTask;
 import com.android.quickstep.util.GroupTask;
 import com.android.quickstep.util.LayoutUtils;
 import com.android.quickstep.util.RecentsAtomicAnimationFactory;
@@ -1394,12 +1396,13 @@
         RemoteAnimationTargets targets = params.getTargetSet();
         if (targets != null && targets.findTask(taskId) != null) {
             launchSideTaskInLiveTileMode(taskId, targets.apps, targets.wallpapers,
-                    targets.nonApps);
+                    targets.nonApps, /* transitionInfo= */ null);
         }
     }
 
     public void launchSideTaskInLiveTileMode(int taskId, RemoteAnimationTarget[] apps,
-            RemoteAnimationTarget[] wallpaper, RemoteAnimationTarget[] nonApps) {
+            RemoteAnimationTarget[] wallpaper, RemoteAnimationTarget[] nonApps,
+            @Nullable TransitionInfo transitionInfo) {
         AnimatorSet anim = new AnimatorSet();
         TaskView taskView = getTaskViewByTaskId(taskId);
         if (taskView == null || !isTaskViewVisible(taskView)) {
@@ -1451,7 +1454,7 @@
         } else {
             TaskViewUtils.composeRecentsLaunchAnimator(anim, taskView, apps, wallpaper, nonApps,
                     true /* launcherClosing */, getStateManager(), this,
-                    getDepthController());
+                    getDepthController(), transitionInfo);
         }
         anim.start();
     }
@@ -1981,12 +1984,7 @@
                         splitTask.getBottomRightTask(), mOrientationState,
                         mTaskOverlayFactory, splitTask.getSplitBounds());
             } else if (taskView instanceof DesktopTaskView desktopTaskView) {
-                // Minimized tasks should not be shown in Overview
-                List<Task> nonMinimizedTasks =
-                        groupTask.getTasks().stream()
-                                .filter(task -> !task.isMinimized)
-                                .toList();
-                desktopTaskView.bind(nonMinimizedTasks, mOrientationState,
+                desktopTaskView.bind((DesktopTask) groupTask, mOrientationState,
                         mTaskOverlayFactory);
             } else if (groupTask instanceof SplitTask splitTask) {
                 Task task = splitTask.getTopLeftTask().key.id == stagedTaskIdToBeRemoved
@@ -3034,7 +3032,7 @@
             final TaskView taskView;
             if (needDesktopTask) {
                 taskView = getTaskViewFromPool(TaskViewType.DESKTOP);
-                ((DesktopTaskView) taskView).bind(Arrays.asList(runningTasks),
+                ((DesktopTaskView) taskView).bind(new DesktopTask(Arrays.asList(runningTasks)),
                         mOrientationState, mTaskOverlayFactory);
             } else if (needGroupTaskView) {
                 taskView = getTaskViewFromPool(TaskViewType.GROUPED);
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index 609262f..34dd8d8 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -91,8 +91,8 @@
 import com.android.quickstep.util.RecentsOrientedState
 import com.android.quickstep.util.TaskCornerRadius
 import com.android.quickstep.util.TaskRemovedDuringLaunchListener
-import com.android.quickstep.util.displayId
 import com.android.quickstep.util.isExternalDisplay
+import com.android.quickstep.util.validDisplayId
 import com.android.quickstep.views.RecentsView.UNBOUND_TASK_VIEW_ID
 import com.android.systemui.shared.recents.model.Task
 import com.android.systemui.shared.recents.model.ThumbnailData
@@ -144,7 +144,7 @@
         get() = this === recentsView?.runningTaskView
 
     val displayId: Int
-        get() = taskContainers.firstOrNull()?.task.displayId
+        get() = taskContainers.firstOrNull()?.task.validDisplayId
 
     val isExternalDisplay: Boolean
         get() = displayId.isExternalDisplay
@@ -1220,6 +1220,7 @@
                 recentsView.stateManager,
                 recentsView,
                 recentsView.depthController,
+                /* transitionInfo= */ null,
             )
             addListener(
                 object : AnimatorListenerAdapter() {
diff --git a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewScreenshotTest.kt b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewScreenshotTest.kt
index 232a08a..c3b4d15 100644
--- a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewScreenshotTest.kt
+++ b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewScreenshotTest.kt
@@ -24,6 +24,7 @@
 import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized
 import com.android.quickstep.task.viewmodel.TaskThumbnailViewModel
 import com.google.android.apps.nexuslauncher.imagecomparison.goldenpathmanager.ViewScreenshotGoldenPathManager
+import org.junit.After
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -47,6 +48,11 @@
 
     private val taskThumbnailViewModel = FakeTaskThumbnailViewModel()
 
+    @After
+    fun tearDown() {
+        RecentsDependencies.destroy()
+    }
+
     @Test
     fun taskThumbnailView_uninitializedByDefault() {
         screenshotRule.screenshotTest("taskThumbnailView_uninitialized") { activity ->
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/model/data/TaskViewItemInfoTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/data/TaskViewItemInfoTest.kt
index de0da64..adfbca5 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/model/data/TaskViewItemInfoTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/data/TaskViewItemInfoTest.kt
@@ -47,6 +47,7 @@
 import com.google.common.truth.Truth.assertThat
 import dagger.BindsInstance
 import dagger.Component
+import org.junit.After
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -78,6 +79,11 @@
         RecentsDependencies.initialize(context)
     }
 
+    @After
+    fun tearDown() {
+        RecentsDependencies.destroy()
+    }
+
     @Test
     fun singleTask() {
         val taskContainers = listOf(createTaskContainer(createTask(1)))
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt
index bfd53ef..2bdc22a 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt
@@ -31,7 +31,6 @@
 import com.android.launcher3.taskbar.TaskbarViewTestUtil.createHotseatItems
 import com.android.launcher3.taskbar.bubbles.BubbleBarViewController
 import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController
-import com.android.launcher3.taskbar.rules.DisplayControllerModule
 import com.android.launcher3.taskbar.rules.MockedRecentsModelHelper
 import com.android.launcher3.taskbar.rules.MockedRecentsModelTestRule
 import com.android.launcher3.taskbar.rules.SandboxParams
@@ -423,9 +422,7 @@
 
 /** TaskbarOverflowComponent used to bind the RecentsModel. */
 @LauncherAppSingleton
-@Component(
-    modules = [AllModulesForTest::class, FakePrefsModule::class, DisplayControllerModule::class]
-)
+@Component(modules = [AllModulesForTest::class, FakePrefsModule::class])
 interface TaskbarOverflowComponent : TaskbarSandboxComponent {
 
     @Component.Builder
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarStashControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarStashControllerTest.kt
index 8395d79..021e1e4 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarStashControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarStashControllerTest.kt
@@ -50,11 +50,8 @@
 import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.InjectController
 import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.UserSetupMode
 import com.android.launcher3.taskbar.rules.TaskbarWindowSandboxContext
-import com.android.launcher3.util.Executors.MAIN_EXECUTOR
 import com.android.launcher3.util.LauncherMultivalentJUnit
 import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices
-import com.android.launcher3.util.TestUtil
-import com.android.quickstep.RecentsAnimationDeviceState
 import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED
 import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_VISIBLE
 import com.android.wm.shell.Flags.FLAG_ENABLE_BUBBLE_BAR
@@ -63,8 +60,6 @@
 import org.junit.After
 import org.junit.Rule
 import org.junit.Test
-import org.junit.rules.TestWatcher
-import org.junit.runner.Description
 import org.junit.runner.RunWith
 
 @RunWith(LauncherMultivalentJUnit::class)
@@ -73,16 +68,7 @@
 class TaskbarStashControllerTest {
     @get:Rule(order = 0) val setFlagsRule = SetFlagsRule()
     @get:Rule(order = 1) val context = TaskbarWindowSandboxContext.create()
-    @get:Rule(order = 2)
-    val initSingleton =
-        object : TestWatcher() {
-            override fun starting(description: Description?) {
-                TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {
-                    RecentsAnimationDeviceState.INSTANCE[context].systemUiStateFlags = 0
-                }
-            }
-        }
-    @get:Rule(order = 3) val taskbarModeRule = TaskbarModeRule(context)
+    @get:Rule(order = 2) val taskbarModeRule = TaskbarModeRule(context)
     @get:Rule(order = 4) val animatorTestRule = AnimatorTestRule(this)
     @get:Rule(order = 5) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context)
 
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRule.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRule.kt
index 3cf912c..482d735 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRule.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRule.kt
@@ -61,7 +61,7 @@
                 val mode = taskbarMode.mode
 
                 getInstrumentation().runOnMainSync {
-                    DisplayController.INSTANCE[context].let {
+                    DisplayController.get(context).let {
                         if (it is DisplayControllerSpy) {
                             it.infoModifier = { info ->
                                 spy(info) {
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt
index 95e8980..62d6fdc 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt
@@ -26,11 +26,12 @@
 import com.android.launcher3.dagger.ApplicationContext
 import com.android.launcher3.dagger.LauncherAppComponent
 import com.android.launcher3.dagger.LauncherAppSingleton
-import com.android.launcher3.util.AllModulesForTest
+import com.android.launcher3.util.AllModulesMinusPerDisplayObjectProvider
 import com.android.launcher3.util.DaggerSingletonTracker
 import com.android.launcher3.util.DisplayController
 import com.android.launcher3.util.FakePrefsModule
 import com.android.launcher3.util.MainThreadInitializedObject.ObjectSandbox
+import com.android.launcher3.util.PerDisplayObjectProvider
 import com.android.launcher3.util.SandboxApplication
 import com.android.launcher3.util.SettingsCache
 import com.android.launcher3.util.SettingsCacheSandbox
@@ -131,14 +132,31 @@
     override fun getInfo(): Info = infoModifier?.invoke(super.getInfo()) ?: super.getInfo()
 }
 
+@LauncherAppSingleton
+class PerDisplayObjectProviderImpl
+@Inject
+constructor(private val displayController: DisplayControllerSpy) : PerDisplayObjectProvider {
+    override fun getDisplayController(displayId: Int): DisplayController {
+        return displayController
+    }
+}
+
 @Module
-abstract class DisplayControllerModule {
-    @Binds abstract fun bindDisplayController(controller: DisplayControllerSpy): DisplayController
+abstract class PerDisplayObjectProviderModule {
+    @Binds
+    abstract fun bindPerDisplayObjectProvider(
+        impl: PerDisplayObjectProviderImpl
+    ): PerDisplayObjectProvider
 }
 
 @LauncherAppSingleton
 @Component(
-    modules = [AllModulesForTest::class, FakePrefsModule::class, DisplayControllerModule::class]
+    modules =
+        [
+            AllModulesMinusPerDisplayObjectProvider::class,
+            FakePrefsModule::class,
+            PerDisplayObjectProviderModule::class,
+        ]
 )
 interface TaskbarSandboxComponent : LauncherAppComponent {
 
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/util/SettingsCacheSandbox.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/util/SettingsCacheSandbox.kt
index dcd5352..52238c8 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/util/SettingsCacheSandbox.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/util/SettingsCacheSandbox.kt
@@ -17,19 +17,22 @@
 package com.android.launcher3.util
 
 import android.net.Uri
+import com.android.launcher3.util.SettingsCache.OnChangeListener
 import org.mockito.kotlin.any
 import org.mockito.kotlin.doAnswer
 import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
 
-/**
- * Provides a sandboxed [SettingsCache] for testing.
- *
- * Note that listeners registered to [cache] will never be invoked.
- */
+/** Provides [SettingsCache] sandboxed from system settings for testing. */
 class SettingsCacheSandbox {
     private val values = mutableMapOf<Uri, Int>()
+    private val listeners = mutableMapOf<Uri, MutableSet<OnChangeListener>>()
 
-    /** Fake cache that delegates [SettingsCache.getValue] to [values]. */
+    /**
+     * Fake cache that delegates:
+     * - [SettingsCache.getValue] to [values]
+     * - [SettingsCache.mListenerMap] to [listeners].
+     */
     val cache =
         mock<SettingsCache> {
             on { getValue(any<Uri>()) } doAnswer { mock.getValue(it.getArgument(0), 1) }
@@ -37,11 +40,22 @@
                 {
                     values.getOrDefault(it.getArgument(0), it.getArgument(1)) == 1
                 }
+
+            doAnswer {
+                    listeners.getOrPut(it.getArgument(0)) { mutableSetOf() }.add(it.getArgument(1))
+                }
+                .whenever(mock)
+                .register(any(), any())
+            doAnswer { listeners[it.getArgument(0)]?.remove(it.getArgument(1)) }
+                .whenever(mock)
+                .unregister(any(), any())
         }
 
     operator fun get(key: Uri): Int? = values[key]
 
     operator fun set(key: Uri, value: Int) {
+        if (value == values[key]) return
         values[key] = value
+        listeners[key]?.forEach { it.onSettingsChanged(value == 1) }
     }
 }
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/AllAppsActionManagerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/AllAppsActionManagerTest.kt
index 73b35e8..a1bd107 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/AllAppsActionManagerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/AllAppsActionManagerTest.kt
@@ -18,32 +18,59 @@
 
 import android.app.PendingIntent
 import android.content.IIntentSender
+import android.provider.Settings
+import android.provider.Settings.Secure.USER_SETUP_COMPLETE
 import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.dagger.LauncherAppComponent
+import com.android.launcher3.dagger.LauncherAppSingleton
+import com.android.launcher3.util.AllModulesForTest
 import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR
+import com.android.launcher3.util.SandboxApplication
+import com.android.launcher3.util.SettingsCache
+import com.android.launcher3.util.SettingsCacheSandbox
 import com.android.launcher3.util.TestUtil
 import com.google.common.truth.Truth.assertThat
+import dagger.BindsInstance
+import dagger.Component
 import java.util.concurrent.Semaphore
 import java.util.concurrent.TimeUnit.SECONDS
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 
 private const val TIMEOUT = 5L
+private val USER_SETUP_COMPLETE_URI = Settings.Secure.getUriFor(USER_SETUP_COMPLETE)
 
 @RunWith(AndroidJUnit4::class)
 class AllAppsActionManagerTest {
     private val callbackSemaphore = Semaphore(0)
     private val bgExecutor = UI_HELPER_EXECUTOR
 
-    private val allAppsActionManager =
-        AllAppsActionManager(
-            InstrumentationRegistry.getInstrumentation().targetContext,
-            bgExecutor,
-        ) {
-            callbackSemaphore.release()
-            PendingIntent(IIntentSender.Default())
+    @get:Rule val context = SandboxApplication()
+
+    private val settingsCacheSandbox =
+        SettingsCacheSandbox().also { it[USER_SETUP_COMPLETE_URI] = 1 }
+
+    private val allAppsActionManager by
+        lazy(LazyThreadSafetyMode.NONE) {
+            AllAppsActionManager(context, bgExecutor) {
+                callbackSemaphore.release()
+                PendingIntent(IIntentSender.Default())
+            }
         }
 
+    @Before
+    fun initDaggerComponent() {
+        context.initDaggerComponent(
+            DaggerAllAppsActionManagerTestComponent.builder()
+                .bindSettingsCache(settingsCacheSandbox.cache)
+        )
+    }
+
+    @After fun destroyManager() = allAppsActionManager.onDestroy()
+
     @Test
     fun taskbarPresent_actionRegistered() {
         allAppsActionManager.isTaskbarPresent = true
@@ -88,4 +115,50 @@
         assertThat(callbackSemaphore.tryAcquire(TIMEOUT, SECONDS)).isTrue()
         assertThat(allAppsActionManager.isActionRegistered).isTrue()
     }
+
+    @Test
+    fun taskbarPresent_userSetupIncomplete_actionUnregistered() {
+        settingsCacheSandbox[USER_SETUP_COMPLETE_URI] = 0
+        allAppsActionManager.isTaskbarPresent = true
+        assertThat(allAppsActionManager.isActionRegistered).isFalse()
+    }
+
+    @Test
+    fun taskbarPresent_setupUiVisible_actionUnregistered() {
+        allAppsActionManager.isSetupUiVisible = true
+        allAppsActionManager.isTaskbarPresent = true
+        assertThat(allAppsActionManager.isActionRegistered).isFalse()
+    }
+
+    @Test
+    fun taskbarPresent_userSetupCompleted_actionRegistered() {
+        settingsCacheSandbox[USER_SETUP_COMPLETE_URI] = 0
+        allAppsActionManager.isTaskbarPresent = true
+
+        settingsCacheSandbox[USER_SETUP_COMPLETE_URI] = 1
+        assertThat(callbackSemaphore.tryAcquire(TIMEOUT, SECONDS)).isTrue()
+        assertThat(allAppsActionManager.isActionRegistered).isTrue()
+    }
+
+    @Test
+    fun taskbarPresent_setupUiDismissed_actionRegistered() {
+        allAppsActionManager.isSetupUiVisible = true
+        allAppsActionManager.isTaskbarPresent = true
+
+        allAppsActionManager.isSetupUiVisible = false
+        assertThat(callbackSemaphore.tryAcquire(TIMEOUT, SECONDS)).isTrue()
+        assertThat(allAppsActionManager.isActionRegistered).isTrue()
+    }
+}
+
+@LauncherAppSingleton
+@Component(modules = [AllModulesForTest::class])
+interface AllAppsActionManagerTestComponent : LauncherAppComponent {
+
+    @Component.Builder
+    interface Builder : LauncherAppComponent.Builder {
+        @BindsInstance fun bindSettingsCache(settingsCache: SettingsCache): Builder
+
+        override fun build(): AllAppsActionManagerTestComponent
+    }
 }
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt
index 63115bf..f5de2b0 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt
@@ -60,7 +60,7 @@
             RecentsAnimationDeviceState(
                 context,
                 exclusionManager,
-                component.displayController,
+                component.perDisplayObjectProvider,
                 component.contextualSearchStateManager,
                 component.rotationTouchHelper,
                 component.settingsCache,
@@ -150,7 +150,7 @@
 
         allSysUiStates().forEach { state ->
             val canStartGesture = !disablingStates.contains(state)
-            underTest.systemUiStateFlags = state
+            underTest.setSystemUiFlags(state)
             assertThat(underTest.canStartTrackpadGesture()).isEqualTo(canStartGesture)
         }
     }
@@ -166,7 +166,7 @@
             )
 
         stateToExpectedResult.forEach { (state, allowed) ->
-            underTest.systemUiStateFlags = state
+            underTest.setSystemUiFlags(state)
             assertThat(underTest.canStartTrackpadGesture()).isEqualTo(allowed)
         }
     }
@@ -177,7 +177,7 @@
 
         allSysUiStates().forEach { state ->
             val canStartGesture = !disablingStates.contains(state)
-            underTest.systemUiStateFlags = state
+            underTest.setSystemUiFlags(state)
             assertThat(underTest.canStartSystemGesture()).isEqualTo(canStartGesture)
         }
     }
@@ -197,7 +197,7 @@
             )
 
         stateToExpectedResult.forEach { (state, gestureAllowed) ->
-            underTest.systemUiStateFlags = state
+            underTest.setSystemUiFlags(state)
             assertThat(underTest.canStartSystemGesture()).isEqualTo(gestureAllowed)
         }
     }
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsWindowSwipeHandlerTestCase.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsWindowSwipeHandlerTestCase.java
index c1be1ce..e875e4d 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsWindowSwipeHandlerTestCase.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsWindowSwipeHandlerTestCase.java
@@ -25,15 +25,12 @@
 import com.android.launcher3.util.LauncherMultivalentJUnit;
 import com.android.quickstep.fallback.FallbackRecentsView;
 import com.android.quickstep.fallback.RecentsState;
-import com.android.quickstep.fallback.window.RecentsDisplayModel;
 import com.android.quickstep.fallback.window.RecentsWindowManager;
 import com.android.quickstep.fallback.window.RecentsWindowSwipeHandler;
 import com.android.quickstep.views.RecentsViewContainer;
 
-import dagger.BindsInstance;
 import dagger.Component;
 
-import org.junit.Before;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 
@@ -46,16 +43,9 @@
         RecentsWindowSwipeHandler,
         FallbackWindowInterface> {
 
-    @Mock private RecentsDisplayModel mRecentsDisplayModel;
     @Mock private FallbackRecentsView<RecentsWindowManager> mRecentsView;
     @Mock private RecentsWindowManager mRecentsWindowManager;
 
-    @Before
-    public void setRecentsDisplayModel() {
-        mContext.initDaggerComponent(DaggerRecentsWindowSwipeHandlerTestCase_TestComponent.builder()
-                .bindRecentsDisplayModel(mRecentsDisplayModel));
-    }
-
     @NonNull
     @Override
     protected RecentsWindowSwipeHandler createSwipeHandler(long touchTimeMs,
@@ -87,7 +77,6 @@
     interface TestComponent extends LauncherAppComponent {
         @Component.Builder
         interface Builder extends LauncherAppComponent.Builder {
-            @BindsInstance Builder bindRecentsDisplayModel(RecentsDisplayModel model);
             @Override LauncherAppComponent build();
         }
     }
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/TaskAnimationManagerTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/TaskAnimationManagerTest.java
index b730f5b..6e9885a 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/TaskAnimationManagerTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/TaskAnimationManagerTest.java
@@ -54,7 +54,12 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mTaskAnimationManager = new TaskAnimationManager(mContext,
-                RecentsAnimationDeviceState.INSTANCE.get(mContext), mSystemUiProxy);
+                RecentsAnimationDeviceState.INSTANCE.get(mContext)) {
+            @Override
+            SystemUiProxy getSystemUiProxy() {
+                return mSystemUiProxy;
+            }
+        };
     }
 
     @Test
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumerTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumerTest.java
index 7776351..71d377b 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumerTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumerTest.java
@@ -433,7 +433,7 @@
                 DaggerNavHandleLongPressInputConsumerTest_TopTaskTrackerComponent
                         .builder()
                         .bindTopTaskTracker(mTopTaskTracker));
-        mScreenWidth = DisplayController.INSTANCE.get(mContext).getInfo().currentSize.x;
+        mScreenWidth = DisplayController.get(mContext).getInfo().currentSize.x;
         mUnderTest = new NavHandleLongPressInputConsumer(mContext, mDelegate, mInputMonitor,
                 mDeviceState, mNavHandle, mGestureState);
         mUnderTest.setNavHandleLongPressHandler(mNavHandleLongPressHandler);
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/logging/SettingsChangeLoggerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/logging/SettingsChangeLoggerTest.kt
index 14570b5..2671b80 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/logging/SettingsChangeLoggerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/logging/SettingsChangeLoggerTest.kt
@@ -64,7 +64,7 @@
 
     @Mock private lateinit var mMockLogger: StatsLogManager.StatsLogger
     @Mock private lateinit var mTracker: DaggerSingletonTracker
-    private var displayController: DisplayController = DisplayController.INSTANCE.get(mContext)
+    private var displayController: DisplayController = DisplayController.get(mContext)
     private var settingsCache: SettingsCache = SettingsCache.INSTANCE.get(mContext)
 
     @Captor private lateinit var mEventCaptor: ArgumentCaptor<StatsLogManager.EventEnum>
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/ContextualSearchInvokerTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/ContextualSearchInvokerTest.java
index 4e49aec..61971b1 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/ContextualSearchInvokerTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/ContextualSearchInvokerTest.java
@@ -50,8 +50,7 @@
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.quickstep.BaseContainerInterface;
 import com.android.quickstep.DeviceConfigWrapper;
-import com.android.quickstep.OverviewComponentObserver;
-import com.android.quickstep.RecentsAnimationDeviceState;
+import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.TopTaskTracker;
 import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
 import com.android.quickstep.views.RecentsView;
@@ -76,7 +75,7 @@
     private @Mock PackageManager mMockPackageManager;
     private @Mock ContextualSearchStateManager mMockStateManager;
     private @Mock TopTaskTracker mMockTopTaskTracker;
-    private @Mock RecentsAnimationDeviceState mMockDeviceState;
+    private @Mock SystemUiProxy mMockSystemUiProxy;
     private @Mock StatsLogManager mMockStatsLogManager;
     private @Mock StatsLogManager.StatsLogger mMockStatsLogger;
     private @Mock ContextualSearchHapticManager mMockContextualSearchHapticManager;
@@ -85,7 +84,6 @@
     private @Mock RecentsViewContainer mMockRecentsViewContainer;
     private @Mock RecentsView mMockRecentsView;
     private @Mock RecentsPagedOrientationHandler mMockOrientationHandler;
-    private @Mock OverviewComponentObserver mMockOverviewComponentObserver;
     private ContextualSearchInvoker mContextualSearchInvoker;
 
     @Before
@@ -94,21 +92,20 @@
         when(mMockPackageManager.hasSystemFeature(FEATURE_CONTEXTUAL_SEARCH)).thenReturn(true);
         Context context = spy(getApplicationContext());
         doReturn(mMockPackageManager).when(context).getPackageManager();
-        when(mMockDeviceState.getSystemUiStateFlags()).thenReturn(0L);
+        when(mMockSystemUiProxy.getLastSystemUiStateFlags()).thenReturn(0L);
         when(mMockTopTaskTracker.getRunningSplitTaskIds()).thenReturn(new int[]{});
         when(mMockStateManager.isContextualSearchIntentAvailable()).thenReturn(true);
         when(mMockStateManager.isContextualSearchSettingEnabled()).thenReturn(true);
         when(mMockStatsLogManager.logger()).thenReturn(mMockStatsLogger);
-
-        doReturn(mMockContainerInterface).when(mMockOverviewComponentObserver)
-                .getContainerInterface();
         when(mMockContainerInterface.getCreatedContainer()).thenReturn(mMockRecentsViewContainer);
         when(mMockRecentsViewContainer.getOverviewPanel()).thenReturn(mMockRecentsView);
 
-        mContextualSearchInvoker = new ContextualSearchInvoker(context, mMockStateManager,
-                mMockTopTaskTracker, mMockDeviceState, mMockStatsLogManager,
-                mMockContextualSearchHapticManager, mMockOverviewComponentObserver,
-                mMockContextualSearchManager);
+        mContextualSearchInvoker = spy(new ContextualSearchInvoker(context, mMockStateManager,
+                mMockTopTaskTracker, mMockSystemUiProxy, mMockStatsLogManager,
+                mMockContextualSearchHapticManager, mMockContextualSearchManager
+        ));
+        doReturn(mMockContainerInterface).when(mContextualSearchInvoker)
+                .getRecentsContainerInterface();
     }
 
     @Test
@@ -151,7 +148,7 @@
 
     @Test
     public void runContextualSearchInvocationChecksAndLogFailures_notificationShadeIsShowing() {
-        when(mMockDeviceState.getSystemUiStateFlags()).thenReturn(SHADE_EXPANDED_SYSUI_FLAGS);
+        when(mMockSystemUiProxy.getLastSystemUiStateFlags()).thenReturn(SHADE_EXPANDED_SYSUI_FLAGS);
 
         assertFalse("Expected invocation checks to fail when notification shade is showing",
                 mContextualSearchInvoker.runContextualSearchInvocationChecksAndLogFailures());
@@ -161,7 +158,7 @@
 
     @Test
     public void runContextualSearchInvocationChecksAndLogFailures_keyguardIsShowing() {
-        when(mMockDeviceState.getSystemUiStateFlags()).thenReturn(
+        when(mMockSystemUiProxy.getLastSystemUiStateFlags()).thenReturn(
                 KEYGUARD_SHOWING_SYSUI_FLAGS);
 
         assertFalse("Expected invocation checks to fail when keyguard is showing",
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskViewSimulatorTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
index be76f9e..ab48065 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
@@ -16,6 +16,7 @@
 package com.android.quickstep.util;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -37,11 +38,12 @@
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.dagger.LauncherAppComponent;
 import com.android.launcher3.dagger.LauncherAppSingleton;
-import com.android.launcher3.util.AllModulesMinusWMProxy;
+import com.android.launcher3.util.AllModulesMinusWMProxyAndPerDisplayObjectProvider;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.DisplayController.Info;
 import com.android.launcher3.util.LauncherModelHelper;
 import com.android.launcher3.util.NavigationMode;
+import com.android.launcher3.util.PerDisplayObjectProvider;
 import com.android.launcher3.util.RotationUtils;
 import com.android.launcher3.util.WindowBounds;
 import com.android.launcher3.util.window.CachedDisplayInfo;
@@ -166,10 +168,11 @@
             LauncherModelHelper helper = new LauncherModelHelper();
             try {
                 DisplayController mockController = mock(DisplayController.class);
+                PerDisplayObjectProvider objectProvider = mock(PerDisplayObjectProvider.class);
 
                 helper.sandboxContext.initDaggerComponent(
                         DaggerTaskViewSimulatorTest_TaskViewSimulatorTestComponent.builder()
-                                .bindDisplayController(mockController));
+                                .bindPerDisplayObjectProvider(objectProvider));
                 int rotation = mDisplaySize.x > mDisplaySize.y
                         ? Surface.ROTATION_90 : Surface.ROTATION_0;
                 CachedDisplayInfo cdi = new CachedDisplayInfo(mDisplaySize, rotation);
@@ -201,6 +204,8 @@
                 Context configurationContext = helper.sandboxContext.createConfigurationContext(
                         configuration);
 
+                when(objectProvider.getDisplayController(anyInt())).thenReturn(
+                        mockController);
                 DisplayController.Info info = new Info(
                         configurationContext, wmProxy, perDisplayBoundsCache);
                 when(mockController.getInfo()).thenReturn(info);
@@ -281,14 +286,14 @@
     }
 
     @LauncherAppSingleton
-    @Component(modules = {AllModulesMinusWMProxy.class})
+    @Component(modules = {AllModulesMinusWMProxyAndPerDisplayObjectProvider.class})
     interface TaskViewSimulatorTestComponent extends LauncherAppComponent {
 
         @Component.Builder
         interface Builder extends LauncherAppComponent.Builder {
 
             @BindsInstance
-            Builder bindDisplayController(DisplayController controller);
+            Builder bindPerDisplayObjectProvider(PerDisplayObjectProvider provider);
 
             TaskViewSimulatorTestComponent build();
         }
diff --git a/quickstep/tests/src/com/android/quickstep/InputConsumerUtilsTest.java b/quickstep/tests/src/com/android/quickstep/InputConsumerUtilsTest.java
index a164dad..e2ca91a 100644
--- a/quickstep/tests/src/com/android/quickstep/InputConsumerUtilsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/InputConsumerUtilsTest.java
@@ -89,6 +89,7 @@
 import org.mockito.junit.MockitoRule;
 
 import java.util.Optional;
+import java.util.function.Function;
 
 import javax.inject.Provider;
 
@@ -103,6 +104,7 @@
     private TaskAnimationManager mTaskAnimationManager;
     private InputChannelCompat.InputEventReceiver mInputEventReceiver;
     @Nullable private ResetGestureInputConsumer mResetGestureInputConsumer;
+    @NonNull private Function<GestureState, AnimatedFloat> mSwipeUpProxyProvider = (state) -> null;
 
     @NonNull @Mock private TaskbarActivityContext mTaskbarActivityContext;
     @NonNull @Mock private OverviewComponentObserver mOverviewComponentObserver;
@@ -444,7 +446,7 @@
 
     @Test
     public void testNewConsumer_withSwipeUpProxyProvider_returnsProgressDelegateInputConsumer() {
-        doReturn(new AnimatedFloat()).when(mDeviceState).getSwipeUpProxy(any());
+        mSwipeUpProxyProvider = (state) -> new AnimatedFloat();
 
         assertCorrectInputConsumer(
                 this::createInputConsumer,
@@ -494,6 +496,7 @@
                 otherActivityInputConsumer -> {},
                 mInputEventReceiver,
                 mTaskbarManager,
+                mSwipeUpProxyProvider,
                 mOverviewCommandHelper,
                 event);
 
diff --git a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
index c713c3d..06c8745 100644
--- a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
+++ b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
@@ -71,9 +71,6 @@
 
     private final LauncherInstrumentation mLauncher;
 
-    static final DisplayController DISPLAY_CONTROLLER =
-            DisplayController.INSTANCE.get(getInstrumentation().getTargetContext());
-
     public NavigationModeSwitchRule(LauncherInstrumentation launcher) {
         mLauncher = launcher;
     }
@@ -168,11 +165,13 @@
                             latch.countDown();
                         }
                     };
+            DisplayController displayController =
+                    DisplayController.get(getInstrumentation().getTargetContext());
             targetContext.getMainExecutor().execute(() ->
-                    DISPLAY_CONTROLLER.addChangeListener(listener));
+                    displayController.addChangeListener(listener));
             latch.await(60, TimeUnit.SECONDS);
             targetContext.getMainExecutor().execute(() ->
-                    DISPLAY_CONTROLLER.removeChangeListener(listener));
+                    displayController.removeChangeListener(listener));
 
             assertTrue(launcher, "Navigation mode didn't change to " + expectedMode,
                     currentSysUiNavigationMode() == expectedMode, description);
diff --git a/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java b/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
index a0ec635..649b222 100644
--- a/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
+++ b/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
@@ -17,6 +17,8 @@
 
 package com.android.quickstep;
 
+import static android.view.Display.DEFAULT_DISPLAY;
+
 import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
 
 import static com.android.launcher3.util.NavigationMode.NO_BUTTON;
@@ -45,6 +47,7 @@
 import com.android.launcher3.testing.shared.ResourceUtils;
 import com.android.launcher3.util.DaggerSingletonTracker;
 import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.PerDisplayObjectProvider;
 import com.android.launcher3.util.RotationUtils;
 import com.android.launcher3.util.WindowBounds;
 import com.android.launcher3.util.window.CachedDisplayInfo;
@@ -300,9 +303,14 @@
     @Test
     public void testSimpleOrientationTouchTransformer() {
         final DisplayController displayController = mock(DisplayController.class);
+        final PerDisplayObjectProvider displayControllerProvider = mock(
+                PerDisplayObjectProvider.class);
+        doReturn(displayController).when(displayControllerProvider).getDisplayController(
+                DEFAULT_DISPLAY);
         doReturn(mInfo).when(displayController).getInfo();
         final SimpleOrientationTouchTransformer transformer =
-                new SimpleOrientationTouchTransformer(getApplicationContext(), displayController,
+                new SimpleOrientationTouchTransformer(getApplicationContext(),
+                        displayControllerProvider,
                         mock(DaggerSingletonTracker.class));
         final MotionEvent move1 = generateMotionEvent(MotionEvent.ACTION_MOVE, 100, 10);
         transformer.transform(move1, Surface.ROTATION_90);
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index 8b7033f..ff8a541 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -143,7 +143,7 @@
     <string name="title_change_settings" msgid="1376365968844349552">"Promijeni postavke"</string>
     <string name="notification_dots_service_title" msgid="4284221181793592871">"Prikaži tačke za obavještenja"</string>
     <string name="developer_options_title" msgid="700788437593726194">"Opcije za programere"</string>
-    <string name="auto_add_shortcuts_label" msgid="4926805029653694105">"Dodaj ikone aplikacija na početni ekran"</string>
+    <string name="auto_add_shortcuts_label" msgid="4926805029653694105">"Dodavanje ikona aplikacija na početni ekran"</string>
     <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Za nove aplikacije"</string>
     <string name="package_state_unknown" msgid="7592128424511031410">"Nepoznato"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"Ukloni"</string>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index a1e822a..0c10f0d 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -129,7 +129,7 @@
     <string name="app_pair_name_format" msgid="8134106404716224054">"Ζεύγος εφαρμογών: <xliff:g id="APP1">%1$s</xliff:g> και <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Ταπετσαρία και στιλ"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Επεξεργασία αρχικής οθόνης"</string>
-    <string name="settings_button_text" msgid="8873672322605444408">"Ρυθμίσεις Αρχ. Οθ."</string>
+    <string name="settings_button_text" msgid="8873672322605444408">"Ρυθμ. Αρχικής οθόνης"</string>
     <string name="msg_disabled_by_admin" msgid="6898038085516271325">"Απενεργοποιήθηκε από τον διαχειριστή σας"</string>
     <string name="allow_rotation_title" msgid="7222049633713050106">"Να επιτρέπεται η περιστροφή της αρχικής οθόνης"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Όταν το τηλέφωνο περιστρέφεται"</string>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index db2b601..8696ff8 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -50,7 +50,7 @@
     <string name="add_to_home_screen" msgid="9168649446635919791">"होम स्क्रीन पर जोड़ें"</string>
     <string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> विजेट को होम स्क्रीन पर जोड़ा गया"</string>
     <string name="suggested_widgets_header_title" msgid="1844314680798145222">"सुझाव"</string>
-    <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"ज़रूरी ऐप्लिकेशन"</string>
+    <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"अहम जानकारी"</string>
     <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"खबरों और पत्रिकाओं वाले ऐप्लिकेशन"</string>
     <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"मनोरंजन से जुड़े ऐप्लिकेशन"</string>
     <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"सोशल मीडिया ऐप्लिकेशन"</string>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index 9f4ac5a..1a1f405 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -54,7 +54,7 @@
     <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Újságok és magazinok"</string>
     <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Szórakozás"</string>
     <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Közösségi"</string>
-    <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Neked javasolt"</string>
+    <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Javaslatok"</string>
     <string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"A <xliff:g id="SELECTED_HEADER">%1$s</xliff:g>-modulok a jobb, a kereső és a beállítások pedig a bal oldalon találhatók"</string>
     <string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# modul}other{# modul}}"</string>
     <string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# gyorsparancs}other{# gyorsparancs}}"</string>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index ec6da15..34bfbad 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -93,7 +93,7 @@
     <string name="all_apps_button_personal_label" msgid="1315764287305224468">"רשימת אפליקציות אישיות"</string>
     <string name="all_apps_button_work_label" msgid="7270707118948892488">"רשימת אפליקציות עבודה"</string>
     <string name="remove_drop_target_label" msgid="7812859488053230776">"הסרה"</string>
-    <string name="uninstall_drop_target_label" msgid="4722034217958379417">"להסרת התקנה"</string>
+    <string name="uninstall_drop_target_label" msgid="4722034217958379417">"הסרת ההתקנה"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"פרטי האפליקציה"</string>
     <string name="install_private_system_shortcut_label" msgid="1616889277073184841">"התקנה במרחב הפרטי"</string>
     <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"הסרת האפליקציה"</string>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index b23b97b..bd792ac 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -129,7 +129,7 @@
     <string name="app_pair_name_format" msgid="8134106404716224054">"Programų pora: „<xliff:g id="APP1">%1$s</xliff:g>“ ir „<xliff:g id="APP2">%2$s</xliff:g>“"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Ekrano fonas ir stilius"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Redaguoti pagrindinį ekraną"</string>
-    <string name="settings_button_text" msgid="8873672322605444408">"„Home“ nustatymai"</string>
+    <string name="settings_button_text" msgid="8873672322605444408">"Pagrindinio ekrano nustatymai"</string>
     <string name="msg_disabled_by_admin" msgid="6898038085516271325">"Išjungė administratorius"</string>
     <string name="allow_rotation_title" msgid="7222049633713050106">"Leisti pasukti pagrindinį ekraną"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Kai telefonas pasukamas"</string>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index e3bde3a..de509df 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -136,7 +136,7 @@
     <string name="landscape_mode_title" msgid="5138814555934843926">"ल्यान्डस्केप मोड"</string>
     <string name="landscape_mode_desc" msgid="7372569859592816793">"फोनमा ल्यान्डस्केप मोड अन गर्नुहोस्"</string>
     <string name="notification_dots_title" msgid="9062440428204120317">"नोटिफिकेसन डट"</string>
-    <string name="notification_dots_desc_on" msgid="1679848116452218908">"सक्रिय"</string>
+    <string name="notification_dots_desc_on" msgid="1679848116452218908">"अन छ"</string>
     <string name="notification_dots_desc_off" msgid="1760796511504341095">"निष्क्रिय"</string>
     <string name="title_missing_notification_access" msgid="7503287056163941064">"सूचनासम्बन्धी पहुँच आवश्यक हुन्छ"</string>
     <string name="msg_missing_notification_access" msgid="281113995110910548">"नोटिफिकेसन डट देखाउन <xliff:g id="NAME">%1$s</xliff:g> को एपसम्बन्धी सूचनाहरूलाई अन गर्नुहोस्"</string>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index d771c6f..92b0c6d 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -129,7 +129,7 @@
     <string name="app_pair_name_format" msgid="8134106404716224054">"App-paar: <xliff:g id="APP1">%1$s</xliff:g> en <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Achtergrond en stijl"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Startscherm bewerken"</string>
-    <string name="settings_button_text" msgid="8873672322605444408">"Instellingen start"</string>
+    <string name="settings_button_text" msgid="8873672322605444408">"Instellingen Start"</string>
     <string name="msg_disabled_by_admin" msgid="6898038085516271325">"Uitgezet door je beheerder"</string>
     <string name="allow_rotation_title" msgid="7222049633713050106">"Draaien van startscherm toestaan"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Als de telefoon gedraaid is"</string>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index ac5f3be..c15e2e9 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -50,7 +50,7 @@
     <string name="add_to_home_screen" msgid="9168649446635919791">"ਹੋਮ ਸਕ੍ਰੀਨ \'ਤੇ ਸ਼ਾਮਲ ਕਰੋ"</string>
     <string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> ਵਿਜੇਟ ਨੂੰ ਹੋਮ ਸਕ੍ਰੀਨ \'ਤੇ ਸ਼ਾਮਲ ਕੀਤਾ ਗਿਆ"</string>
     <string name="suggested_widgets_header_title" msgid="1844314680798145222">"ਸੁਝਾਅ"</string>
-    <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"ਲੋੜੀਂਦੀਆਂ"</string>
+    <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"ਲੋੜੀਂਦੀਆਂ ਐਪਾਂ ਦੇ ਵਿਜੇਟ"</string>
     <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"ਖਬਰਾਂ ਅਤੇ ਰਸਾਲੇ"</string>
     <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"ਮਨੋਰੰਜਨ"</string>
     <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"ਸੋਸ਼ਲ"</string>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index b2dd767..f019544 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -49,7 +49,7 @@
     <string name="add_item_request_drag_hint" msgid="8730547755622776606">"Нажмите на виджет и удерживайте его, чтобы переместить в нужное место на главном экране."</string>
     <string name="add_to_home_screen" msgid="9168649446635919791">"Добавить на главный экран"</string>
     <string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Виджет \"<xliff:g id="WIDGET_NAME">%1$s</xliff:g>\" добавлен на главный экран"</string>
-    <string name="suggested_widgets_header_title" msgid="1844314680798145222">"Рекомендованные"</string>
+    <string name="suggested_widgets_header_title" msgid="1844314680798145222">"Рекомендации"</string>
     <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Основное"</string>
     <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Новости и журналы"</string>
     <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Развлечения"</string>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index b2d2a1b..cecfedb 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -46,7 +46,7 @@
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"宽 %1$d,高 %2$d"</string>
     <string name="widget_preview_context_description" msgid="9045841361655787574">"“<xliff:g id="WIDGET_NAME">%1$s</xliff:g>”微件"</string>
     <string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"“<xliff:g id="WIDGET_NAME">%1$s</xliff:g>”微件,宽 %2$d,高 %3$d"</string>
-    <string name="add_item_request_drag_hint" msgid="8730547755622776606">"轻触并按住此微件即可在主屏幕上随意移动"</string>
+    <string name="add_item_request_drag_hint" msgid="8730547755622776606">"轻触并按住此微件即可将其拖拽到主屏幕上任意位置"</string>
     <string name="add_to_home_screen" msgid="9168649446635919791">"添加到主屏幕"</string>
     <string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"已将“<xliff:g id="WIDGET_NAME">%1$s</xliff:g>”微件添加到主屏幕"</string>
     <string name="suggested_widgets_header_title" msgid="1844314680798145222">"建议"</string>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index f740489..a22f943 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -268,8 +268,13 @@
         <attr name="allAppsCellSpecsTwoPanelId" format="reference" />
         <!-- defaults to false, if not specified -->
         <attr name="isFixedLandscape" format="boolean" />
-        <!-- defaults to false, if not specified -->
-        <attr name="isOldGrid" format="boolean" />
+        <!-- By default all grid types are enabled -->
+        <attr name="gridType" format="integer">
+            <!-- Enable on phone only -->
+            <flag name="one_grid" value="1" />
+            <!-- Enable on tablets only -->
+            <flag name="non_one_grid" value="2" />
+        </attr>
 
         <!-- By default all categories are enabled -->
         <attr name="deviceCategory" format="integer">
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index 6277e41..db4480a 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -178,6 +178,8 @@
 
     private ActionMode mCurrentActionMode;
 
+    private DisplayController mDisplayController;
+
     @Override
     public ViewCache getViewCache() {
         return mViewCache;
@@ -224,7 +226,8 @@
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         registerBackDispatcher();
-        DisplayController.INSTANCE.get(this).addChangeListener(this);
+        mDisplayController = DisplayController.get(this);
+        mDisplayController.addChangeListener(this);
     }
 
     @Override
@@ -272,7 +275,9 @@
     protected void onDestroy() {
         super.onDestroy();
         mEventCallbacks[EVENT_DESTROYED].executeAllAndClear();
-        DisplayController.INSTANCE.get(this).removeChangeListener(this);
+        if (mDisplayController != null) {
+            mDisplayController.removeChangeListener(this);
+        }
     }
 
     @Override
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 900f74d..32a3ed7 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3;
 
+import static android.view.Display.DEFAULT_DISPLAY;
+
 import static com.android.launcher3.LauncherPrefs.DB_FILE;
 import static com.android.launcher3.LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE;
 import static com.android.launcher3.LauncherPrefs.FIXED_LANDSCAPE_MODE;
@@ -256,7 +258,7 @@
         String gridName = getCurrentGridName(context);
         initGrid(context, gridName);
 
-        DisplayController dc = DisplayController.INSTANCE.get(context);
+        DisplayController dc = DisplayController.get(context, DEFAULT_DISPLAY);
         dc.setPriorityListener(
                 (displayContext, info, flags) -> {
                     if ((flags & (CHANGE_DENSITY | CHANGE_SUPPORTED_BOUNDS
@@ -315,7 +317,7 @@
         String gridName = getCurrentGridName(context);
 
         // Get the display info based on default display and interpolate it to existing display
-        Info defaultInfo = DisplayController.INSTANCE.get(context).getInfo();
+        Info defaultInfo = DisplayController.get(context, display.getDisplayId()).getInfo();
         @DeviceType int defaultDeviceType = defaultInfo.getDeviceType();
         DisplayOption defaultDisplayOption = invDistWeightedInterpolate(
                 defaultInfo,
@@ -374,7 +376,7 @@
                 + ", dbFile:" + dbFile
                 + ", LauncherPrefs GRID_NAME:" + LauncherPrefs.get(context).get(GRID_NAME)
                 + ", LauncherPrefs DB_FILE:" + LauncherPrefs.get(context).get(DB_FILE));
-        Info displayInfo = DisplayController.INSTANCE.get(context).getInfo();
+        Info displayInfo = DisplayController.get(context).getInfo();
         List<DisplayOption> allOptions = getPredefinedDeviceProfiles(
                 context,
                 gridName,
@@ -994,6 +996,9 @@
         private static final int DEVICE_CATEGORY_PHONE = 1 << 0;
         private static final int DEVICE_CATEGORY_TABLET = 1 << 1;
         private static final int DEVICE_CATEGORY_MULTI_DISPLAY = 1 << 2;
+        private static final int GRID_TYPE_ONE_GRID = 1 << 0;
+        private static final int GRID_TYPE_NON_ONE_GRID = 1 << 1;
+        private static final int GRID_TYPE_ALL = 1 << 2;
         private static final int DEVICE_CATEGORY_ALL =
                 DEVICE_CATEGORY_PHONE | DEVICE_CATEGORY_TABLET | DEVICE_CATEGORY_MULTI_DISPLAY;
 
@@ -1010,6 +1015,7 @@
         public final int numColumns;
         public final int numSearchContainerColumns;
         public final int deviceCategory;
+        public final int gridType;
 
         private final int[] numFolderRows = new int[COUNT_SIZES];
         private final int[] numFolderColumns = new int[COUNT_SIZES];
@@ -1048,7 +1054,6 @@
         private final int mAllAppsCellSpecsTwoPanelId;
         private final int mGridSizeSpecsId;
         private final boolean mIsFixedLandscape;
-        private final boolean mIsOldGrid;
 
         public GridOption(Context context, AttributeSet attrs, Info displayInfo) {
             TypedArray a = context.obtainStyledAttributes(
@@ -1196,7 +1201,7 @@
             }
 
             mIsFixedLandscape = a.getBoolean(R.styleable.GridDisplayOption_isFixedLandscape, false);
-            mIsOldGrid = a.getBoolean(R.styleable.GridDisplayOption_isOldGrid, false);
+            gridType = a.getInt(R.styleable.GridDisplayOption_gridType, GRID_TYPE_ALL);
 
             int inlineForRotation = a.getInt(R.styleable.GridDisplayOption_inlineQsb,
                     DONT_INLINE_QSB);
@@ -1241,13 +1246,11 @@
                 return mIsFixedLandscape && isFixedLandscape && Flags.oneGridSpecs();
             }
 
-            // Here we return true if we want to show the new grids.
-            if (mGridSizeSpecsId != INVALID_RESOURCE_HANDLE) {
+            // If the grid type is one grid we return true when the flag is on, if the grid type
+            // is non-one grid we return true when the flag is off. Otherwise, we return true.
+            if (gridType == GRID_TYPE_ONE_GRID) {
                 return Flags.oneGridSpecs();
-            }
-
-            // Here we return true if we want to show the old grids.
-            if (mIsOldGrid) {
+            } else if (gridType == GRID_TYPE_NON_ONE_GRID) {
                 return !Flags.oneGridSpecs();
             }
 
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 04e699c..315301a 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -349,7 +349,8 @@
 
     private final int[] mTmpAddItemCellCoordinates = new int[2];
 
-    protected Hotseat mHotseat;
+    @Thunk
+    Hotseat mHotseat;
 
     private DropTargetBar mDropTargetBar;
 
@@ -806,6 +807,10 @@
         );
     }
 
+    public void onAssistantVisibilityChanged(float visibility) {
+        mHotseat.getQsb().setAlpha(1f - visibility);
+    }
+
     /**
      * Returns {@code true} if a new DeviceProfile is initialized, and {@code false} otherwise.
      */
diff --git a/src/com/android/launcher3/LauncherPrefs.kt b/src/com/android/launcher3/LauncherPrefs.kt
index 2a5cd63..fba94fd 100644
--- a/src/com/android/launcher3/LauncherPrefs.kt
+++ b/src/com/android/launcher3/LauncherPrefs.kt
@@ -299,7 +299,7 @@
         @JvmField
         val ALLOW_ROTATION =
             backedUpItem(RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY, Boolean::class.java) {
-                RotationHelper.getAllowRotationDefaultValue(DisplayController.INSTANCE.get(it).info)
+                RotationHelper.getAllowRotationDefaultValue(DisplayController.get(it).info)
             }
 
         @JvmField
diff --git a/src/com/android/launcher3/dagger/LauncherAppModule.java b/src/com/android/launcher3/dagger/LauncherAppModule.java
index c58a414..ece0ff0 100644
--- a/src/com/android/launcher3/dagger/LauncherAppModule.java
+++ b/src/com/android/launcher3/dagger/LauncherAppModule.java
@@ -23,6 +23,7 @@
         ApiWrapperModule.class,
         PluginManagerWrapperModule.class,
         StaticObjectModule.class,
+        PerDisplayObjectProviderModule.class,
         AppModule.class
 })
 public class LauncherAppModule {
diff --git a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
index 7bd7c3e..fe23093 100644
--- a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
+++ b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
@@ -26,11 +26,11 @@
 import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.util.ApiWrapper;
 import com.android.launcher3.util.DaggerSingletonTracker;
-import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.DynamicResource;
 import com.android.launcher3.util.LockedUserState;
 import com.android.launcher3.util.MSDLPlayerWrapper;
 import com.android.launcher3.util.PackageManagerHelper;
+import com.android.launcher3.util.PerDisplayObjectProvider;
 import com.android.launcher3.util.PluginManagerWrapper;
 import com.android.launcher3.util.ScreenOnTracker;
 import com.android.launcher3.util.SettingsCache;
@@ -69,7 +69,7 @@
     LauncherPrefs getLauncherPrefs();
     ThemeManager getThemeManager();
     UserCache getUserCache();
-    DisplayController getDisplayController();
+    PerDisplayObjectProvider getPerDisplayObjectProvider();
     WallpaperColorHints getWallpaperColorHints();
     LockedUserState getLockedUserState();
 
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index 911064c..4c39aa0 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -284,7 +284,7 @@
      * Returns the insets of the screen closest to the display given by the context
      */
     private Rect getInsets(Context context) {
-        DisplayController.Info info = DisplayController.INSTANCE.get(context).getInfo();
+        DisplayController.Info info = DisplayController.get(context).getInfo();
         float maxDiff = Float.MAX_VALUE;
         Display display = context.getDisplay();
         Rect insets = new Rect();
diff --git a/src/com/android/launcher3/icons/LauncherIcons.java b/src/com/android/launcher3/icons/LauncherIcons.java
deleted file mode 100644
index 5c6debe..0000000
--- a/src/com/android/launcher3/icons/LauncherIcons.java
+++ /dev/null
@@ -1,164 +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.icons;
-
-import static android.graphics.Color.BLACK;
-
-import static com.android.launcher3.graphics.ThemeManager.PREF_ICON_SHAPE;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Path;
-import android.graphics.Rect;
-import android.graphics.drawable.AdaptiveIconDrawable;
-import android.os.UserHandle;
-
-import androidx.annotation.NonNull;
-
-import com.android.launcher3.Flags;
-import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.LauncherPrefs;
-import com.android.launcher3.graphics.IconShape;
-import com.android.launcher3.graphics.ThemeManager;
-import com.android.launcher3.pm.UserCache;
-import com.android.launcher3.util.MainThreadInitializedObject;
-import com.android.launcher3.util.SafeCloseable;
-import com.android.launcher3.util.UserIconInfo;
-
-import java.util.concurrent.ConcurrentLinkedQueue;
-
-/**
- * Wrapper class to provide access to {@link BaseIconFactory} and also to provide pool of this class
- * that are threadsafe.
- */
-public class LauncherIcons extends BaseIconFactory implements AutoCloseable {
-
-    private static final float SEVEN_SIDED_COOKIE_SCALE = 72f / 80f;
-    private static final float FOUR_SIDED_COOKIE_SCALE = 72f / 83.4f;
-    private static final float VERY_SUNNY_SCALE = 72f / 92f;
-    private static final float DEFAULT_ICON_SCALE = 1f;
-
-
-    private static final MainThreadInitializedObject<Pool> POOL =
-            new MainThreadInitializedObject<>(Pool::new);
-
-    /**
-     * Return a new Message instance from the global pool. Allows us to
-     * avoid allocating new objects in many cases.
-     */
-    public static LauncherIcons obtain(Context context) {
-        return POOL.get(context).obtain();
-    }
-
-    public static void clearPool(Context context) {
-        POOL.get(context).close();
-    }
-
-    private final ConcurrentLinkedQueue<LauncherIcons> mPool;
-
-    protected LauncherIcons(Context context, int fillResIconDpi, int iconBitmapSize,
-            ConcurrentLinkedQueue<LauncherIcons> pool) {
-        super(context, fillResIconDpi, iconBitmapSize);
-        mThemeController = ThemeManager.INSTANCE.get(context).getThemeController();
-        mPool = pool;
-    }
-
-    /**
-     * Recycles a LauncherIcons that may be in-use.
-     */
-    public void recycle() {
-        clear();
-        mPool.add(this);
-    }
-
-    @NonNull
-    @Override
-    protected UserIconInfo getUserInfo(@NonNull UserHandle user) {
-        return UserCache.INSTANCE.get(mContext).getUserInfo(user);
-    }
-
-    @NonNull
-    @Override
-    public Path getShapePath(AdaptiveIconDrawable drawable, Rect iconBounds) {
-        if (!Flags.enableLauncherIconShapes()) return drawable.getIconMask();
-        return IconShape.INSTANCE.get(mContext).getShape().getPath(iconBounds);
-    }
-
-    @Override
-    protected void drawAdaptiveIcon(
-            @NonNull Canvas canvas,
-            @NonNull AdaptiveIconDrawable drawable,
-            @NonNull Path overridePath
-    ) {
-        if (!Flags.enableLauncherIconShapes()) {
-            super.drawAdaptiveIcon(canvas, drawable, overridePath);
-            return;
-        }
-        String shapeKey = LauncherPrefs.get(mContext).get(PREF_ICON_SHAPE);
-        float iconScale = switch (shapeKey) {
-            case "seven_sided_cookie" -> SEVEN_SIDED_COOKIE_SCALE;
-            case "four_sided_cookie" -> FOUR_SIDED_COOKIE_SCALE;
-            case "sunny" -> VERY_SUNNY_SCALE;
-            default -> DEFAULT_ICON_SCALE;
-        };
-        canvas.clipPath(overridePath);
-        canvas.drawColor(BLACK);
-        canvas.save();
-        canvas.scale(iconScale, iconScale, canvas.getWidth() / 2f, canvas.getHeight() / 2f);
-        if (drawable.getBackground() != null) {
-            drawable.getBackground().draw(canvas);
-        }
-        if (drawable.getForeground() != null) {
-            drawable.getForeground().draw(canvas);
-        }
-        canvas.restore();
-    }
-
-    @Override
-    public void close() {
-        recycle();
-    }
-
-    private static class Pool implements SafeCloseable {
-
-        private final Context mContext;
-
-        @NonNull
-        private ConcurrentLinkedQueue<LauncherIcons> mPool = new ConcurrentLinkedQueue<>();
-
-        private Pool(Context context) {
-            mContext = context;
-        }
-
-        public LauncherIcons obtain() {
-            ConcurrentLinkedQueue<LauncherIcons> pool = mPool;
-            LauncherIcons m = pool.poll();
-
-            if (m == null) {
-                InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(mContext);
-                return new LauncherIcons(mContext, idp.fillResIconDpi, idp.iconBitmapSize, pool);
-            } else {
-                return m;
-            }
-        }
-
-        @Override
-        public void close() {
-            mPool = new ConcurrentLinkedQueue<>();
-        }
-    }
-}
diff --git a/src/com/android/launcher3/icons/LauncherIcons.kt b/src/com/android/launcher3/icons/LauncherIcons.kt
new file mode 100644
index 0000000..518f29d
--- /dev/null
+++ b/src/com/android/launcher3/icons/LauncherIcons.kt
@@ -0,0 +1,139 @@
+/*
+ * 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.icons
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Path
+import android.graphics.Rect
+import android.graphics.drawable.AdaptiveIconDrawable
+import android.os.UserHandle
+import com.android.launcher3.Flags
+import com.android.launcher3.InvariantDeviceProfile
+import com.android.launcher3.LauncherPrefs
+import com.android.launcher3.graphics.IconShape
+import com.android.launcher3.graphics.ThemeManager
+import com.android.launcher3.pm.UserCache
+import com.android.launcher3.util.MainThreadInitializedObject
+import com.android.launcher3.util.SafeCloseable
+import com.android.launcher3.util.UserIconInfo
+import java.util.concurrent.ConcurrentLinkedQueue
+
+/**
+ * Wrapper class to provide access to [BaseIconFactory] and also to provide pool of this class that
+ * are threadsafe.
+ */
+class LauncherIcons
+protected constructor(
+    context: Context,
+    fillResIconDpi: Int,
+    iconBitmapSize: Int,
+    private val pool: ConcurrentLinkedQueue<LauncherIcons>,
+) : BaseIconFactory(context, fillResIconDpi, iconBitmapSize), AutoCloseable {
+
+    init {
+        mThemeController = ThemeManager.INSTANCE[context].themeController
+    }
+
+    /** Recycles a LauncherIcons that may be in-use. */
+    fun recycle() {
+        clear()
+        pool.add(this)
+    }
+
+    override fun getUserInfo(user: UserHandle): UserIconInfo {
+        return UserCache.INSTANCE[mContext].getUserInfo(user)
+    }
+
+    public override fun getShapePath(drawable: AdaptiveIconDrawable, iconBounds: Rect): Path {
+        if (!Flags.enableLauncherIconShapes()) return drawable.iconMask
+        return IconShape.INSTANCE[mContext].shape.getPath(iconBounds)
+    }
+
+    override fun drawAdaptiveIcon(
+        canvas: Canvas,
+        drawable: AdaptiveIconDrawable,
+        overridePath: Path,
+    ) {
+        if (!Flags.enableLauncherIconShapes()) {
+            super.drawAdaptiveIcon(canvas, drawable, overridePath)
+            return
+        }
+        val shapeKey = LauncherPrefs.get(mContext).get(ThemeManager.PREF_ICON_SHAPE)
+        val iconScale =
+            when (shapeKey) {
+                "seven_sided_cookie" -> SEVEN_SIDED_COOKIE_SCALE
+                "four_sided_cookie" -> FOUR_SIDED_COOKIE_SCALE
+                "sunny" -> VERY_SUNNY_SCALE
+                else -> DEFAULT_ICON_SCALE
+            }
+        canvas.clipPath(overridePath)
+        canvas.drawColor(Color.BLACK)
+        canvas.save()
+        canvas.scale(iconScale, iconScale, canvas.width / 2f, canvas.height / 2f)
+        if (drawable.background != null) {
+            drawable.background.draw(canvas)
+        }
+        if (drawable.foreground != null) {
+            drawable.foreground.draw(canvas)
+        }
+        canvas.restore()
+    }
+
+    override fun close() {
+        recycle()
+    }
+
+    private class Pool(private val context: Context) : SafeCloseable {
+        private var pool = ConcurrentLinkedQueue<LauncherIcons>()
+
+        fun obtain(): LauncherIcons {
+            val pool = pool
+            return pool.poll()
+                ?: InvariantDeviceProfile.INSTANCE[context].let {
+                    LauncherIcons(context, it.fillResIconDpi, it.iconBitmapSize, pool)
+                }
+        }
+
+        override fun close() {
+            pool = ConcurrentLinkedQueue()
+        }
+    }
+
+    companion object {
+        private const val SEVEN_SIDED_COOKIE_SCALE = 72f / 80f
+        private const val FOUR_SIDED_COOKIE_SCALE = 72f / 83.4f
+        private const val VERY_SUNNY_SCALE = 72f / 92f
+        private const val DEFAULT_ICON_SCALE = 1f
+
+        private val POOL = MainThreadInitializedObject { Pool(it) }
+
+        /**
+         * Return a new Message instance from the global pool. Allows us to avoid allocating new
+         * objects in many cases.
+         */
+        @JvmStatic
+        fun obtain(context: Context): LauncherIcons {
+            return POOL[context].obtain()
+        }
+
+        @JvmStatic
+        fun clearPool(context: Context) {
+            POOL[context].close()
+        }
+    }
+}
diff --git a/src/com/android/launcher3/settings/SettingsActivity.java b/src/com/android/launcher3/settings/SettingsActivity.java
index 6008287..1771627 100644
--- a/src/com/android/launcher3/settings/SettingsActivity.java
+++ b/src/com/android/launcher3/settings/SettingsActivity.java
@@ -292,7 +292,7 @@
          * will remove that preference from the list.
          */
         protected boolean initPreference(Preference preference) {
-            DisplayController.Info info = DisplayController.INSTANCE.get(getContext()).getInfo();
+            DisplayController.Info info = DisplayController.get(getContext()).getInfo();
             switch (preference.getKey()) {
                 case NOTIFICATION_DOTS_PREFERENCE_KEY:
                     return BuildConfig.NOTIFICATION_DOTS_ENABLED;
diff --git a/src/com/android/launcher3/states/RotationHelper.java b/src/com/android/launcher3/states/RotationHelper.java
index 9376518..f6017c1 100644
--- a/src/com/android/launcher3/states/RotationHelper.java
+++ b/src/com/android/launcher3/states/RotationHelper.java
@@ -189,7 +189,7 @@
     public void initialize() {
         if (mInitialized) return;
         mInitialized = true;
-        DisplayController displayController = DisplayController.INSTANCE.get(mActivity);
+        DisplayController displayController = DisplayController.get(mActivity);
         DisplayController.Info info = displayController.getInfo();
         setIgnoreAutoRotateSettings(info.isTablet(info.realBounds));
         displayController.addChangeListener(this);
@@ -201,7 +201,7 @@
         if (mDestroyed) return;
         mDestroyed = true;
         mActivity.removeOnDeviceProfileChangeListener(this);
-        DisplayController.INSTANCE.get(mActivity).removeChangeListener(this);
+        DisplayController.get(mActivity).removeChangeListener(this);
         LauncherPrefs.get(mActivity).removeListener(this, ALLOW_ROTATION);
     }
 
diff --git a/src/com/android/launcher3/util/DaggerSingletonTracker.java b/src/com/android/launcher3/util/DaggerSingletonTracker.java
index b7a88db..1de32e0 100644
--- a/src/com/android/launcher3/util/DaggerSingletonTracker.java
+++ b/src/com/android/launcher3/util/DaggerSingletonTracker.java
@@ -38,7 +38,7 @@
     private boolean mClosed = false;
 
     @Inject
-    DaggerSingletonTracker() {
+    public DaggerSingletonTracker() {
     }
 
     /**
diff --git a/src/com/android/launcher3/util/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java
index ee1af81..5ad22bf 100644
--- a/src/com/android/launcher3/util/DisplayController.java
+++ b/src/com/android/launcher3/util/DisplayController.java
@@ -16,6 +16,7 @@
 package com.android.launcher3.util;
 
 import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.INVALID_DISPLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 
 import static com.android.launcher3.InvariantDeviceProfile.TYPE_MULTI_DISPLAY;
@@ -55,7 +56,6 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.dagger.ApplicationContext;
 import com.android.launcher3.dagger.LauncherAppComponent;
-import com.android.launcher3.dagger.LauncherAppSingleton;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.util.window.CachedDisplayInfo;
 import com.android.launcher3.util.window.WindowManagerProxy;
@@ -71,13 +71,10 @@
 import java.util.StringJoiner;
 import java.util.concurrent.CopyOnWriteArrayList;
 
-import javax.inject.Inject;
-
 /**
  * Utility class to cache properties of default display to avoid a system RPC on every call.
  */
 @SuppressLint("NewApi")
-@LauncherAppSingleton
 public class DisplayController implements ComponentCallbacks,
         DesktopVisibilityListener {
 
@@ -89,8 +86,8 @@
     // TODO(b/254119092) remove all logs with this tag
     public static final String TASKBAR_NOT_DESTROYED_TAG = "b/254119092";
 
-    public static final DaggerSingletonObject<DisplayController> INSTANCE =
-            new DaggerSingletonObject<>(LauncherAppComponent::getDisplayController);
+    public static final DaggerSingletonObject<PerDisplayObjectProvider> PROVIDER =
+            new DaggerSingletonObject<>(LauncherAppComponent::getPerDisplayObjectProvider);
 
     public static final int CHANGE_ACTIVE_SCREEN = 1 << 0;
     public static final int CHANGE_ROTATION = 1 << 1;
@@ -125,14 +122,54 @@
 
     private Info mInfo;
     private boolean mDestroyed = false;
+    private final int mDisplayId;
 
-    @Inject
-    protected DisplayController(@ApplicationContext Context context,
+    /**
+     * Get a DisplayController associated with the given Context.
+     * @param context the context (must return a valid display id)
+     * @return the DisplayController instance associated with the display id of the context
+     */
+    public static DisplayController get(Context context) {
+        int displayId = DEFAULT_DISPLAY;
+        if (context != null) {
+            try {
+                displayId = context.getDisplay().getDisplayId();
+            } catch (UnsupportedOperationException ignored) {
+                Log.w(TAG, "DisplayController access from non-display context");
+            }
+        }
+        if (displayId == INVALID_DISPLAY) {
+            displayId = DEFAULT_DISPLAY;
+        }
+        return PROVIDER.get(context).getDisplayController(displayId);
+    }
+
+    /**
+     * Get a DisplayController associated with the given display id.
+     * @param context a context
+     * @param displayId a display id
+     * @return the DisplayController instance associated with the given display id
+     */
+    public static DisplayController get(Context context, int displayId) {
+        return PROVIDER.get(context).getDisplayController(displayId);
+    }
+
+    @VisibleForTesting
+    public DisplayController(@ApplicationContext Context context,
             WindowManagerProxy wmProxy,
             LauncherPrefs prefs,
             DaggerSingletonTracker lifecycle) {
+        this(context, wmProxy, prefs, lifecycle, DEFAULT_DISPLAY);
+    }
+
+    public DisplayController(@ApplicationContext Context context,
+            WindowManagerProxy wmProxy,
+            LauncherPrefs prefs,
+            DaggerSingletonTracker lifecycle,
+            int displayId) {
         mContext = context;
         mWMProxy = wmProxy;
+        mDisplayId = displayId;
 
         if (enableTaskbarPinning()) {
             LauncherPrefChangeListener prefListener = key -> {
@@ -150,11 +187,17 @@
             prefs.addListener(prefListener, TASKBAR_PINNING);
             prefs.addListener(prefListener, TASKBAR_PINNING_IN_DESKTOP_MODE);
             lifecycle.addCloseable(() -> prefs.removeListener(
-                        prefListener, TASKBAR_PINNING, TASKBAR_PINNING_IN_DESKTOP_MODE));
+                    prefListener, TASKBAR_PINNING, TASKBAR_PINNING_IN_DESKTOP_MODE));
         }
 
         Display display = context.getSystemService(DisplayManager.class)
-                .getDisplay(DEFAULT_DISPLAY);
+                .getDisplay(displayId);
+        if (display == null) {
+            // Race when a display is rapidly added then removed.
+            mWindowContext = null;
+            mInfo = null;
+            return;
+        }
         mWindowContext = mContext.createWindowContext(display, TYPE_APPLICATION, null);
         mWindowContext.registerComponentCallbacks(this);
 
@@ -174,20 +217,25 @@
         });
     }
 
-    /**
-     * Returns the current navigation mode
-     */
-    public static NavigationMode getNavigationMode(Context context) {
-        return INSTANCE.get(context).getInfo().getNavigationMode();
+    public int getDisplayId() {
+        return mDisplayId;
     }
 
     /**
-     * Returns whether taskbar is transient or persistent.
+     * Returns the current navigation mode for the display associated with the given Context.
+     */
+    public static NavigationMode getNavigationMode(Context context) {
+        return get(context).getInfo().getNavigationMode();
+    }
+
+    /**
+     * Returns whether taskbar is transient or persistent  for the display associated with the given
+     * Context.
      *
      * @return {@code true} if transient, {@code false} if persistent.
      */
     public static boolean isTransientTaskbar(Context context) {
-        return INSTANCE.get(context).getInfo().isTransientTaskbar();
+        return get(context).getInfo().isTransientTaskbar();
     }
 
     /**
@@ -207,25 +255,27 @@
     }
 
     /**
-     * Returns whether the taskbar is pinned in gesture navigation mode.
+     * Returns whether the taskbar is pinned in gesture navigation mode for the display associated
+     * with the given Context.
      */
     public static boolean isPinnedTaskbar(Context context) {
-        return INSTANCE.get(context).getInfo().isPinnedTaskbar();
+        return get(context).getInfo().isPinnedTaskbar();
     }
 
     /**
-     * Returns whether the taskbar is forced to be pinned when home is visible.
+     * Returns whether the taskbar is forced to be pinned when home is visible for the display
+     * associated with the given Context.
      */
     public static boolean showLockedTaskbarOnHome(Context context) {
-        return INSTANCE.get(context).getInfo().showLockedTaskbarOnHome();
+        return get(context).getInfo().showLockedTaskbarOnHome();
     }
 
     /**
      * Returns whether desktop taskbar (pinned taskbar that shows desktop tasks) is to be used
-     * on the display because the display is a freeform display.
+     * on the display associated with the given Context because the display is a freeform display.
      */
     public static boolean showDesktopTaskbarForFreeformDisplay(Context context) {
-        return INSTANCE.get(context).getInfo().showDesktopTaskbarForFreeformDisplay();
+        return get(context).getInfo().showDesktopTaskbarForFreeformDisplay();
     }
 
     @Override
@@ -261,6 +311,7 @@
     @Override
     public final void onConfigurationChanged(Configuration config) {
         Log.d(TASKBAR_NOT_DESTROYED_TAG, "DisplayController#onConfigurationChanged: " + config);
+        if (mWindowContext == null || mInfo == null) return;
         if (config.densityDpi != mInfo.densityDpi
                 || config.fontScale != mInfo.fontScale
                 || !mInfo.mScreenSizeDp.equals(
@@ -295,6 +346,7 @@
 
     @AnyThread
     public void notifyConfigChange() {
+        if (mWindowContext == null || mInfo == null) return;
         Info oldInfo = mInfo;
 
         Context displayInfoContext = mWindowContext;
@@ -348,6 +400,7 @@
     }
 
     private void notifyChange(Context context, int flags) {
+        if (mInfo == null) return;
         if (mPriorityListener != null) {
             mPriorityListener.onDisplayInfoChanged(context, mInfo, flags);
         }
@@ -582,6 +635,7 @@
      * Dumps the current state information
      */
     public void dump(PrintWriter pw) {
+        if (mInfo == null) return;
         Info info = mInfo;
         pw.println("DisplayController.Info:");
         pw.println("  normalizedDisplayInfo=" + info.normalizedDisplayInfo);
diff --git a/src/com/android/launcher3/util/PerDisplayObjectProvider.java b/src/com/android/launcher3/util/PerDisplayObjectProvider.java
new file mode 100644
index 0000000..8cb4e20
--- /dev/null
+++ b/src/com/android/launcher3/util/PerDisplayObjectProvider.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2025 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;
+
+/**
+ * Interface for providers of objects for which there is one per display. The lifecycle of the
+ * object is for the time the display is connected or is that of the app, whichever is shorter.
+ */
+public interface PerDisplayObjectProvider {
+    /**
+     * Get the DisplayController the given display id.
+     *
+     * @param displayId The display id
+     * @return Returns the display controller if the display id is valid and otherwise throws an
+     * IllegalArgumentException.
+     */
+    DisplayController getDisplayController(int displayId);
+}
diff --git a/src_no_quickstep/com/android/launcher3/dagger/Modules.kt b/src_no_quickstep/com/android/launcher3/dagger/Modules.kt
index c3bf7c5..8872b79 100644
--- a/src_no_quickstep/com/android/launcher3/dagger/Modules.kt
+++ b/src_no_quickstep/com/android/launcher3/dagger/Modules.kt
@@ -16,7 +16,16 @@
 
 package com.android.launcher3.dagger
 
+import android.content.Context
+import android.view.Display.DEFAULT_DISPLAY
+import com.android.launcher3.LauncherPrefs
+import com.android.launcher3.util.DaggerSingletonTracker
+import com.android.launcher3.util.DisplayController
+import com.android.launcher3.util.PerDisplayObjectProvider
+import com.android.launcher3.util.window.WindowManagerProxy
+import dagger.Binds
 import dagger.Module
+import javax.inject.Inject
 
 private object Modules {}
 
@@ -28,5 +37,30 @@
 
 @Module object StaticObjectModule {}
 
+@LauncherAppSingleton
+class DefaultPerDisplayObjectProvider
+@Inject
+constructor(
+    @ApplicationContext context: Context,
+    wmProxy: WindowManagerProxy,
+    launcherPrefs: LauncherPrefs,
+    lifecycleTracker: DaggerSingletonTracker,
+) : PerDisplayObjectProvider {
+    val displayController =
+        DisplayController(context, wmProxy, launcherPrefs, lifecycleTracker, DEFAULT_DISPLAY)
+
+    override fun getDisplayController(displayId: Int): DisplayController {
+        return displayController
+    }
+}
+
+@Module
+abstract class PerDisplayObjectProviderModule {
+    @Binds
+    abstract fun bindPerDisplayObjectProvider(
+        impl: DefaultPerDisplayObjectProvider
+    ): PerDisplayObjectProvider
+}
+
 // Module containing bindings for the final derivative app
 @Module abstract class AppModule {}
diff --git a/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt b/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
index 9c64ec9..56a0543 100644
--- a/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
@@ -25,16 +25,18 @@
 import android.platform.test.rule.IgnoreLimit
 import android.platform.test.rule.LimitDevicesRule
 import android.util.DisplayMetrics
+import android.view.Display.DEFAULT_DISPLAY
 import android.view.Surface
 import androidx.test.core.app.ApplicationProvider.getApplicationContext
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.launcher3.dagger.LauncherAppComponent
 import com.android.launcher3.dagger.LauncherAppSingleton
 import com.android.launcher3.testing.shared.ResourceUtils
-import com.android.launcher3.util.AllModulesMinusWMProxy
+import com.android.launcher3.util.AllModulesMinusWMProxyAndPerDisplayObjectProvider
 import com.android.launcher3.util.DisplayController
 import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext
 import com.android.launcher3.util.NavigationMode
+import com.android.launcher3.util.PerDisplayObjectProvider
 import com.android.launcher3.util.WindowBounds
 import com.android.launcher3.util.rule.TestStabilityRule
 import com.android.launcher3.util.rule.setFlags
@@ -68,6 +70,7 @@
     protected val testContext: Context = InstrumentationRegistry.getInstrumentation().context
     protected lateinit var context: SandboxContext
     protected open val runningContext: Context = getApplicationContext()
+    private val displayControllerProvider: PerDisplayObjectProvider = mock()
     private val displayController: DisplayController = mock()
     private val windowManagerProxy: WindowManagerProxy = mock()
     private val launcherPrefs: LauncherPrefs = mock()
@@ -312,7 +315,7 @@
             DaggerAbsDPTestSandboxComponent.builder()
                 .bindWMProxy(windowManagerProxy)
                 .bindLauncherPrefs(launcherPrefs)
-                .bindDisplayController(displayController)
+                .bindDisplayControllerProvider(displayControllerProvider)
         )
 
         whenever(launcherPrefs.get(LauncherPrefs.TASKBAR_PINNING)).thenReturn(false)
@@ -323,6 +326,8 @@
         whenever(launcherPrefs.get(LauncherPrefs.WORKSPACE_SIZE)).thenReturn("")
         whenever(launcherPrefs.get(LauncherPrefs.DB_FILE)).thenReturn("")
         whenever(launcherPrefs.get(LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE)).thenReturn(true)
+        whenever(displayControllerProvider.getDisplayController(DEFAULT_DISPLAY))
+            .thenReturn(displayController)
         val info = spy(DisplayController.Info(context, windowManagerProxy, perDisplayBoundsCache))
         whenever(displayController.info).thenReturn(info)
         whenever(info.isTransientTaskbar).thenReturn(isGestureMode)
@@ -365,7 +370,7 @@
 }
 
 @LauncherAppSingleton
-@Component(modules = [AllModulesMinusWMProxy::class])
+@Component(modules = [AllModulesMinusWMProxyAndPerDisplayObjectProvider::class])
 interface AbsDPTestSandboxComponent : LauncherAppComponent {
 
     @Component.Builder
@@ -374,7 +379,7 @@
 
         @BindsInstance fun bindLauncherPrefs(prefs: LauncherPrefs): Builder
 
-        @BindsInstance fun bindDisplayController(displayController: DisplayController): Builder
+        @BindsInstance fun bindDisplayControllerProvider(dc: PerDisplayObjectProvider): Builder
 
         override fun build(): AbsDPTestSandboxComponent
     }
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/DaggerGraphs.kt b/tests/multivalentTests/src/com/android/launcher3/util/DaggerGraphs.kt
index b66a9d3..8152552 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/DaggerGraphs.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/util/DaggerGraphs.kt
@@ -20,6 +20,7 @@
 import com.android.launcher3.LauncherPrefs
 import com.android.launcher3.dagger.ApiWrapperModule
 import com.android.launcher3.dagger.AppModule
+import com.android.launcher3.dagger.PerDisplayObjectProviderModule
 import com.android.launcher3.dagger.StaticObjectModule
 import com.android.launcher3.dagger.WindowManagerProxyModule
 import dagger.Binds
@@ -39,15 +40,47 @@
             ApiWrapperModule::class,
             WindowManagerProxyModule::class,
             StaticObjectModule::class,
+            PerDisplayObjectProviderModule::class,
             AppModule::class,
         ]
 )
 class AllModulesForTest
 
 /** All modules except the WMProxy */
-@Module(includes = [ApiWrapperModule::class, StaticObjectModule::class, AppModule::class])
+@Module(
+    includes =
+        [
+            ApiWrapperModule::class,
+            StaticObjectModule::class,
+            PerDisplayObjectProviderModule::class,
+            AppModule::class,
+        ]
+)
 class AllModulesMinusWMProxy
 
 /** All modules except the ApiWrapper */
-@Module(includes = [WindowManagerProxyModule::class, StaticObjectModule::class, AppModule::class])
+@Module(
+    includes =
+        [
+            WindowManagerProxyModule::class,
+            StaticObjectModule::class,
+            PerDisplayObjectProviderModule::class,
+            AppModule::class,
+        ]
+)
 class AllModulesMinusApiWrapper
+
+@Module(includes = [ApiWrapperModule::class, StaticObjectModule::class, AppModule::class])
+class AllModulesMinusWMProxyAndPerDisplayObjectProvider
+
+/** All modules except PerDisplayObjectProvider */
+@Module(
+    includes =
+        [
+            ApiWrapperModule::class,
+            WindowManagerProxyModule::class,
+            StaticObjectModule::class,
+            AppModule::class,
+        ]
+)
+class AllModulesMinusPerDisplayObjectProvider
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/DisplayControllerTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/DisplayControllerTest.kt
index 588a668..acf5908 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/DisplayControllerTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/util/DisplayControllerTest.kt
@@ -139,7 +139,7 @@
         whenever(context.resources).thenReturn(resources)
 
         // Initialize DisplayController
-        displayController = DisplayController.INSTANCE.get(context)
+        displayController = DisplayController.get(context)
         displayController.addChangeListener(displayInfoChangeListener)
     }